Jaki jest cel polimorfizmu?
Polimorfizm sprawia, że system typu statycznego jest bardziej elastyczny bez utraty (znacznego) bezpieczeństwa typu statycznego poprzez poluzowanie warunków równoważności typów. Dowód pozostaje, że program będzie działał tylko wtedy, gdy nie będzie zawierał żadnych błędów typu.
Funkcja polimorficzna lub typ danych jest bardziej ogólny niż monomorficzny, ponieważ może być używany w szerszym zakresie scenariuszy. W tym sensie polimorfizm reprezentuje ideę uogólnienia w ściśle typizowanych językach.
Jak to się ma do Javascript?
Javascript ma słaby, dynamiczny system typów. Taki system typów jest równoważny ze ścisłym systemem typów zawierającym tylko jeden typ. Możemy myśleć o takim typie jako o ogromnym typie unii (pseudo składni):
type T =
| Undefined
| Null
| Number
| String
| Boolean
| Symbol
| Object
| Array
| Map
| ...
Każda wartość zostanie skojarzona z jedną z alternatyw tego typu w czasie wykonywania. A ponieważ JavaScript jest napisany słabo, każda wartość może zmienić swój typ dowolną liczbę razy.
Jeśli przyjmiemy perspektywę teoretyczną typu i weźmiemy pod uwagę, że istnieje tylko jeden typ, możemy z całą pewnością stwierdzić, że system typów JavaScript nie ma pojęcia polimorfizmu. Zamiast tego mamy pisanie typu kaczego i niejawne wymuszanie typu.
Ale to nie powinno powstrzymywać nas od myślenia o typach w naszych programach. Ze względu na brak typów w Javascript musimy je wywnioskować podczas procesu kodowania. Nasz umysł musi zastępować brakujący kompilator, tj. Gdy tylko spojrzymy na program, musimy rozpoznać nie tylko algorytmy, ale także podstawowe (być może polimorficzne) typy. Te typy pomogą nam tworzyć bardziej niezawodne i solidniejsze programy.
Aby zrobić to właściwie, przedstawię przegląd najczęstszych przejawów polimorfizmu.
Polimorfizm parametryczny (inaczej rodzajowe)
Polimorfizm parametryczny mówi, że różne typy są wymienne, ponieważ typy w ogóle nie mają znaczenia. Funkcja, która definiuje jeden lub więcej parametrów typu parametrycznego polimorficznego, nie może nic wiedzieć o odpowiednich argumentach, ale traktować je tak samo, ponieważ można je zaadaptować do dowolnego typu. Jest to dość restrykcyjne, ponieważ taka funkcja może działać tylko z tymi właściwościami jej argumentów, które nie są częścią ich danych:
const id = x => x;
id(1);
id("foo");
const k = x => y => x;
const k_ = x => y => y;
k(1) ("foo");
k_(1) ("foo");
const append = x => xs => xs.concat([x]);
append(3) ([1, 2]);
append("c") (["a", "b"]);
Polimorfizm ad-hoc (znany również jako przeciążenie)
Polimorfizm ad hoc mówi, że różne typy są równoważne tylko w określonym celu. Aby być równoważnym w tym sensie, typ musi implementować zestaw funkcji specyficznych dla tego celu. Funkcja, która definiuje jeden lub więcej parametrów typu polimorficznego ad hoc, musi wtedy wiedzieć, które zestawy funkcji są skojarzone z każdym z jej argumentów.
Polimorfizm ad-hoc sprawia, że funkcja jest zgodna z większą domeną typów. Poniższy przykład ilustruje cel „mapowania” i sposób, w jaki typy mogą implementować to ograniczenie. Zamiast zestawu funkcji, ograniczenie „mapowalne” obejmuje tylko jedną map
funkcję:
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();
}
};
const map = f => t => t.map(f, t);
const sqr = x => x * x;
const xs = [1, 2, 3];
const x = new Some(5);
const y = new None();
console.log(
map(sqr) (xs)
);
console.log(
map(sqr) (x)
);
console.log(
map(sqr) (y)
);
Polimorfizm podtypu
Ponieważ inne odpowiedzi obejmują już polimorfizm podtypów, pomijam je.
Strukturalny polimorfizm (inaczej podtyp strutrualny)
Polimorfizm strukturalny mówi, że różne typy są równoważne, jeśli zawierają tę samą strukturę w taki sposób, że jeden typ ma wszystkie właściwości drugiego, ale może zawierać dodatkowe właściwości. Biorąc to pod uwagę, polimorfizm strukturalny jest typowaniem typu kaczego w czasie kompilacji i z pewnością zapewnia dodatkowe bezpieczeństwo typów. Ale twierdząc, że dwie wartości są tego samego typu tylko dlatego, że mają pewne wspólne właściwości, całkowicie ignoruje semantyczny poziom wartości:
const weight = {value: 90, foo: true};
const speed = {value: 90, foo: false, bar: [1, 2, 3]};
Niestety speed
uważany jest za podtyp weight
i gdy tylko porównamy value
właściwości, praktycznie porównujemy jabłka z pomarańczami.