Quel est le but du polymorphisme?
Le polymorphisme rend un système de type statique plus flexible sans perdre la sécurité de type statique (significative) en assouplissant les conditions d'équivalence de type. La preuve reste qu'un programme ne fonctionnera que s'il ne contient aucune erreur de type.
Une fonction polymorphe ou un type de données est plus général qu'un monomorphe, car il peut être utilisé dans un plus large éventail de scénarios. En ce sens, le polymorphisme représente l'idée de généralisation dans des langages strictement typés.
Comment cela s'applique-t-il à Javascript?
Javascript a un système de type faible et dynamique. Un tel système de type est équivalent à un système de type strict ne contenant qu'un seul type. Nous pouvons considérer un tel type comme un énorme type d'union (pseudo syntaxe):
type T =
| Undefined
| Null
| Number
| String
| Boolean
| Symbol
| Object
| Array
| Map
| ...
Chaque valeur sera associée à l'une de ces alternatives de type au moment de l'exécution. Et comme Javascript est faiblement typé, chaque valeur peut changer de type n'importe quel nombre de fois.
Si nous adoptons une perspective théorique de type et considérons qu'il n'y a qu'un seul type, nous pouvons dire avec certitude que le système de types de Javascript n'a pas de notion de polymorphisme. Au lieu de cela, nous avons le typage canard et la coercition de type implicite.
Mais cela ne devrait pas nous empêcher de penser aux types dans nos programmes. En raison du manque de types en Javascript, nous devons les déduire pendant le processus de codage. Notre esprit doit remplacer le compilateur manquant, c'est-à-dire que dès que nous regardons un programme, nous devons reconnaître non seulement les algorithmes, mais aussi les types sous-jacents (peut-être polymorphes). Ces types nous aideront à construire des programmes plus fiables et plus robustes.
Afin de faire cela correctement, je vais vous donner un aperçu des manifestations les plus courantes du polymorphisme.
Polymorphisme paramétrique (aka génériques)
Le polymorphisme paramétrique dit que différents types sont interchangeables parce que les types n'ont pas du tout d'importance. Une fonction qui définit un ou plusieurs paramètres de type polymorphe paramétrique ne doit rien savoir des arguments correspondants mais les traiter tout de même, car ils peuvent adopter n'importe quel type. C'est assez restrictif, car une telle fonction ne peut fonctionner qu'avec les propriétés de ses arguments qui ne font pas partie de leurs données:
// parametric polymorphic functions
const id = x => x;
id(1); // 1
id("foo"); // "foo"
const k = x => y => x;
const k_ = x => y => y;
k(1) ("foo"); // 1
k_(1) ("foo"); // "foo"
const append = x => xs => xs.concat([x]);
append(3) ([1, 2]); // [1, 2, 3]
append("c") (["a", "b"]); // ["a", "b", "c"]
Polymorphisme ad hoc (alias surcharge)
Le polymorphisme ad hoc dit que différents types sont équivalents dans un but précis uniquement. Pour être équivalent dans ce sens, un type doit implémenter un ensemble de fonctions spécifiques à cet objectif. Une fonction qui définit un ou plusieurs paramètres de type polymorphe ad hoc a alors besoin de savoir quels ensembles de fonctions sont associés à chacun de ses arguments.
Le polymorphisme ad hoc rend une fonction compatible avec un plus grand domaine de types. L'exemple suivant illustre l'objectif du «mappage» et la manière dont les types peuvent implémenter cette contrainte. Au lieu d'un ensemble de fonctions, la contrainte "mappable" ne comprend qu'une seule map
fonction:
// Option type
class Option {
cata(pattern, option) {
return pattern[option.constructor.name](option.x);
}
map(f, opt) {
return this.cata({Some: x => new Some(f(x)), None: () => this}, opt);
}
};
class Some extends Option {
constructor(x) {
super(x);
this.x = x;
}
};
class None extends Option {
constructor() {
super();
}
};
// ad-hoc polymorphic function
const map = f => t => t.map(f, t);
// helper/data
const sqr = x => x * x;
const xs = [1, 2, 3];
const x = new Some(5);
const y = new None();
// application
console.log(
map(sqr) (xs) // [1, 4, 9]
);
console.log(
map(sqr) (x) // Some {x: 25}
);
console.log(
map(sqr) (y) // None {}
);
Polymorphisme de sous-type
Puisque d'autres réponses couvrent déjà le polymorphisme de sous-type, je l'ignore.
Polymorphisme structurel (aka sous-typage strutrual)
Le polymorphisme structurel dit que différents types sont équivalents, s'ils contiennent la même structure de telle manière, qu'un type a toutes les propriétés de l'autre mais peut inclure des propriétés supplémentaires. Cela étant dit, le polymorphisme structurel est un type de canard au moment de la compilation et offre certainement une sécurité de type supplémentaire. Mais en prétendant que deux valeurs sont du même type simplement parce qu'elles partagent certaines propriétés, on ignore complètement le niveau sémantique des valeurs:
const weight = {value: 90, foo: true};
const speed = {value: 90, foo: false, bar: [1, 2, 3]};
Malheureusement, speed
est considéré comme un sous-type de weight
et dès que nous comparons les value
propriétés, nous comparons virtuellement des pommes avec des oranges.