Was ist der Zweck des Polymorphismus?
Durch Polymorphismus wird ein statisches Typsystem flexibler, ohne die (signifikante) Sicherheit des statischen Typs zu verlieren, indem die Bedingungen für die Typäquivalenz gelockert werden. Der Beweis bleibt, dass ein Programm nur ausgeführt wird, wenn es keine Typfehler enthält.
Eine polymorphe Funktion oder ein Datentyp ist allgemeiner als eine monomorphe, da sie in einem breiteren Spektrum von Szenarien verwendet werden kann. In diesem Sinne repräsentiert Polymorphismus die Idee der Verallgemeinerung in streng typisierten Sprachen.
Wie trifft dies auf Javascript zu?
Javascript hat ein schwaches, dynamisches Typsystem. Ein solches Typsystem entspricht einem strengen Typsystem, das nur einen Typ enthält. Wir können uns einen solchen Typ als einen riesigen Unionstyp vorstellen (Pseudosyntax):
type T =
| Undefined
| Null
| Number
| String
| Boolean
| Symbol
| Object
| Array
| Map
| ...
Jeder Wert wird zur Laufzeit einer dieser Typalternativen zugeordnet. Und da Javascript schwach typisiert ist, kann jeder Wert seinen Typ beliebig oft ändern.
Wenn wir eine typentheoretische Perspektive einnehmen und bedenken, dass es nur einen Typ gibt, können wir mit Sicherheit sagen, dass das Typensystem von Javascript keinen Begriff von Polymorphismus hat. Stattdessen haben wir Ententypisierung und impliziten Typzwang.
Dies sollte uns jedoch nicht davon abhalten, über Typen in unseren Programmen nachzudenken. Aufgrund des Fehlens von Typen in Javascript müssen wir diese während des Codierungsprozesses ableiten. Unser Verstand muss für den fehlenden Compiler eintreten, dh sobald wir uns ein Programm ansehen, müssen wir nicht nur die Algorithmen, sondern auch die zugrunde liegenden (möglicherweise polymorphen) Typen erkennen. Diese Typen helfen uns, zuverlässigere und robustere Programme zu erstellen.
Um dies richtig zu machen, werde ich Ihnen einen Überblick über die häufigsten Manifestationen des Polymorphismus geben.
Parametrischer Polymorphismus (auch bekannt als Generika)
Der parametrische Polymorphismus besagt, dass verschiedene Typen austauschbar sind, da Typen überhaupt keine Rolle spielen. Eine Funktion, die einen oder mehrere Parameter vom parametrischen polymorphen Typ definiert, darf nichts über die entsprechenden Argumente wissen, sondern sie alle gleich behandeln, da sie für jeden Typ übernommen werden können. Dies ist ziemlich einschränkend, da eine solche Funktion nur mit den Eigenschaften ihrer Argumente arbeiten kann, die nicht Teil ihrer Daten sind:
// 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"]
Ad-hoc-Polymorphismus (auch bekannt als Überladung)
Ad-hoc-Polymorphismus besagt, dass verschiedene Typen nur für einen bestimmten Zweck gleichwertig sind. Um in diesem Sinne gleichwertig zu sein, muss ein Typ eine Reihe von Funktionen implementieren, die für diesen Zweck spezifisch sind. Eine Funktion, die einen oder mehrere Parameter vom polymorphen Ad-hoc-Typ definiert, muss dann wissen, welche Funktionssätze jedem ihrer Argumente zugeordnet sind.
Ad-hoc-Polymorphismus macht eine Funktion mit einer größeren Domäne von Typen kompatibel. Das folgende Beispiel zeigt den Zweck der "Zuordnung" und wie Typen diese Einschränkung implementieren können. Anstelle eines Funktionssatzes enthält die "abbildbare" Einschränkung nur eine einzige map
Funktion:
// 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 {}
);
Subtyp-Polymorphismus
Da andere Antworten bereits den Subtyp-Polymorphismus abdecken, überspringe ich ihn.
Struktureller Polymorphismus (auch bekannt als strutruale Subtypisierung)
Struktureller Polymorphismus besagt, dass verschiedene Typen äquivalent sind, wenn sie dieselbe Struktur so enthalten, dass ein Typ alle Eigenschaften des anderen hat, aber zusätzliche Eigenschaften enthalten kann. Abgesehen davon ist struktureller Polymorphismus Ententypisierung zur Kompilierungszeit und bietet sicherlich zusätzliche Typensicherheit. Indem jedoch behauptet wird, dass zwei Werte vom gleichen Typ sind, nur weil sie einige Eigenschaften gemeinsam haben, wird die semantische Wertebene vollständig ignoriert:
const weight = {value: 90, foo: true};
const speed = {value: 90, foo: false, bar: [1, 2, 3]};
Leider speed
wird dies als Subtyp von angesehen weight
und sobald wir die value
Eigenschaften vergleichen, vergleichen wir Äpfel virtuell mit Orangen.