Qual è lo scopo del polimorfismo?
Il polimorfismo rende un sistema di tipo statico più flessibile senza perdere (significativa) sicurezza del tipo statico allentando le condizioni per l'equivalenza del tipo. La prova rimane che un programma verrà eseguito solo se non contiene errori di tipo.
Una funzione polimorfica o un tipo di dati è più generale di uno monomorfico, perché può essere utilizzato in una gamma più ampia di scenari. In questo senso il polimorfismo rappresenta l'idea di generalizzazione in linguaggi strettamente tipizzati.
Come si applica a Javascript?
Javascript ha un sistema di tipi debole e dinamico. Un tale sistema di tipi è equivalente a un sistema di tipi rigoroso contenente un solo tipo. Possiamo pensare a un tale tipo come a un tipo di unione enorme (pseudo sintassi):
type T =
| Undefined
| Null
| Number
| String
| Boolean
| Symbol
| Object
| Array
| Map
| ...
Ogni valore verrà associato a una di queste alternative di tipo in fase di esecuzione. E poiché Javascript è digitato in modo debole, ogni valore può cambiare il suo tipo un numero qualsiasi di volte.
Se prendiamo una prospettiva teorica dei tipi e consideriamo che esiste un solo tipo, possiamo dire con certezza che il sistema di tipi di Javascript non ha una nozione di polimorfismo. Invece abbiamo la dattilografia e la coercizione del tipo implicito.
Ma questo non dovrebbe impedirci di pensare ai tipi nei nostri programmi. A causa della mancanza di tipi in Javascript, dobbiamo dedurli durante il processo di codifica. La nostra mente deve sostituire il compilatore mancante, cioè non appena guardiamo un programma dobbiamo riconoscere non solo gli algoritmi, ma anche i tipi sottostanti (forse polimorfici). Questi tipi ci aiuteranno a creare programmi più affidabili e robusti.
Per farlo correttamente, ti fornirò una panoramica delle manifestazioni più comuni di polimorfismo.
Polimorfismo parametrico (noto anche come generici)
Il polimorfismo parametrico afferma che tipi diversi sono intercambiabili perché i tipi non contano affatto. Una funzione che definisce uno o più parametri di tipo polimorfico parametrico non deve sapere nulla degli argomenti corrispondenti ma trattarli tutti uguali, perché possono adattarsi a qualsiasi tipo. Questo è abbastanza restrittivo, perché una tale funzione può funzionare solo con quelle proprietà dei suoi argomenti che non fanno parte dei loro dati:
// 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"]
Polimorfismo ad hoc (noto anche come sovraccarico)
Il polimorfismo ad hoc afferma che tipi diversi sono equivalenti solo per uno scopo specifico. Per essere equivalente in questo senso un tipo deve implementare un insieme di funzioni specifiche a tale scopo. Una funzione che definisce uno o più parametri di tipo polimorfico ad hoc deve quindi sapere quali insiemi di funzioni sono associati a ciascuno dei suoi argomenti.
Il polimorfismo ad hoc rende una funzione compatibile con un più ampio dominio di tipi. Il seguente esempio illustra lo scopo della "mappatura" e come i tipi possono implementare questo vincolo. Invece di un insieme di funzioni, il vincolo "mappabile" include solo una singola map
funzione:
// 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 {}
);
Sottotipo polimorfismo
Poiché altre risposte coprono già il polimorfismo del sottotipo, lo salto.
Polimorfismo strutturale (noto anche come sottotipizzazione strutrual)
Il polimorfismo strutturale dice che tipi diversi sono equivalenti, se contengono la stessa struttura in modo tale, che un tipo ha tutte le proprietà dell'altro ma può includere proprietà aggiuntive. Detto questo, il polimorfismo strutturale è la digitazione a papera in fase di compilazione e offre sicuramente qualche sicurezza aggiuntiva. Ma sostenendo che due valori sono dello stesso tipo solo perché condividono alcune proprietà, ignora completamente il livello semantico dei valori:
const weight = {value: 90, foo: true};
const speed = {value: 90, foo: false, bar: [1, 2, 3]};
Purtroppo, speed
è considerato un sottotipo di weight
e non appena confrontiamo le value
proprietà stiamo virtualmente confrontando le mele con le arance.