Qual é o propósito do polimorfismo?
O polimorfismo torna um sistema de tipo estático mais flexível sem perder (significativa) a segurança do tipo estático ao afrouxar as condições de equivalência de tipo. A prova é que um programa só será executado se não contiver erros de tipo.
Uma função polimórfica ou tipo de dados é mais geral do que um monomórfico, porque pode ser usado em uma ampla gama de cenários. Nesse sentido, o polimorfismo representa a ideia de generalização em linguagens estritamente tipadas.
Como isso se aplica ao Javascript?
Javascript tem um sistema de tipo dinâmico e fraco. Esse sistema de tipo é equivalente a um sistema de tipo estrito contendo apenas um tipo. Podemos pensar nesse tipo como um tipo de união enorme (pseudo sintaxe):
type T =
| Undefined
| Null
| Number
| String
| Boolean
| Symbol
| Object
| Array
| Map
| ...
Cada valor será associado a uma dessas alternativas de tipo em tempo de execução. E como o Javascript é mal digitado, cada valor pode mudar seu tipo quantas vezes quiser.
Se tomarmos uma perspectiva teórica de tipo e considerarmos que existe apenas um tipo, podemos dizer com certeza que o sistema de tipos do Javascript não possui uma noção de polimorfismo. Em vez disso, temos a digitação duck e a coerção de tipo implícita.
Mas isso não deve nos impedir de pensar sobre os tipos em nossos programas. Devido à falta de tipos em Javascript, precisamos inferi-los durante o processo de codificação. Nossa mente tem que substituir o compilador ausente, ou seja, assim que olhamos para um programa, devemos reconhecer não apenas os algoritmos, mas também os tipos subjacentes (talvez polimórficos). Esses tipos nos ajudarão a construir programas mais confiáveis e robustos.
Para fazer isso corretamente, vou apresentar uma visão geral das manifestações mais comuns do polimorfismo.
Polimorfismo paramétrico (também conhecido como genérico)
O polimorfismo paramétrico diz que os diferentes tipos são intercambiáveis porque os tipos não importam em absoluto. Uma função que define um ou mais parâmetros do tipo polimórfico paramétrico não deve saber nada sobre os argumentos correspondentes, mas tratá-los todos iguais, pois podem adotar qualquer tipo. Isso é bastante restritivo, porque tal função só pode funcionar com as propriedades de seus argumentos que não fazem parte de seus dados:
// 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 (também conhecido como sobrecarga)
O polimorfismo ad-hoc diz que tipos diferentes são equivalentes apenas para um propósito específico. Para ser equivalente nesse sentido, um tipo deve implementar um conjunto de funções específicas para esse propósito. Uma função que define um ou mais parâmetros do tipo polimórfico ad-hoc precisa saber quais conjuntos de funções estão associados a cada um de seus argumentos.
O polimorfismo ad-hoc torna uma função compatível com um domínio maior de tipos. O exemplo a seguir ilustra a finalidade de "mapeamento" e como os tipos podem implementar essa restrição. Em vez de um conjunto de funções, a restrição "mapeável" inclui apenas uma única map
função:
// 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 {}
);
Polimorfismo de subtipo
Como outras respostas já cobrem o polimorfismo de subtipo, eu pulo.
Polimorfismo estrutural (também conhecido como subtipagem estrutural)
O polimorfismo estrutural diz que tipos diferentes são equivalentes, se contiverem a mesma estrutura de tal forma, que um tipo tenha todas as propriedades do outro, mas pode incluir propriedades adicionais. Dito isso, o polimorfismo estrutural é a digitação reduzida em tempo de compilação e certamente oferece alguma segurança de tipo adicional. Mas, ao afirmar que dois valores são do mesmo tipo apenas porque compartilham algumas propriedades, ele ignora completamente o nível semântico dos valores:
const weight = {value: 90, foo: true};
const speed = {value: 90, foo: false, bar: [1, 2, 3]};
Infelizmente, speed
é considerado um subtipo de weight
e assim que comparamos as value
propriedades, estamos virtualmente comparando maçãs com laranjas.