¿Cuál es el propósito del polimorfismo?
El polimorfismo hace que un sistema de tipo estático sea más flexible sin perder la seguridad (significativa) del tipo estático al aflojar las condiciones para la equivalencia de tipo. La prueba es que un programa solo se ejecutará si no contiene ningún error de tipo.
Una función o tipo de datos polimórficos es más general que uno monomórfico, porque se puede utilizar en una gama más amplia de escenarios. En este sentido, el polimorfismo representa la idea de generalización en lenguajes estrictamente tipificados.
¿Cómo se aplica esto a Javascript?
Javascript tiene un sistema de tipos dinámico y débil. Tal sistema de tipos es equivalente a un sistema de tipos estricto que contiene solo un tipo. Podemos pensar en un tipo como un tipo de unión enorme (pseudo sintaxis):
type T =
| Undefined
| Null
| Number
| String
| Boolean
| Symbol
| Object
| Array
| Map
| ...
Cada valor se asociará a uno de estos tipos de alternativas en tiempo de ejecución. Y dado que Javascript tiene un tipo débil, cada valor puede cambiar su tipo cualquier cantidad de veces.
Si tomamos una perspectiva teórica de tipos y consideramos que solo hay un tipo, podemos decir con certeza que el sistema de tipos de Javascript no tiene una noción de polimorfismo. En su lugar, tenemos tipado pato y coerción de tipo implícita.
Pero esto no debería impedirnos pensar en los tipos de nuestros programas. Debido a la falta de tipos en Javascript, necesitamos inferirlos durante el proceso de codificación. Nuestra mente debe reemplazar al compilador que falta, es decir, tan pronto como miramos un programa, debemos reconocer no solo los algoritmos, sino también los tipos subyacentes (tal vez polimórficos). Estos tipos nos ayudarán a crear programas más fiables y robustos.
Para hacer esto correctamente, les daré una descripción general de las manifestaciones más comunes del polimorfismo.
Polimorfismo paramétrico (también conocido como genéricos)
El polimorfismo paramétrico dice que los diferentes tipos son intercambiables porque los tipos no importan en absoluto. Una función que defina uno o más parámetros de tipo polimórfico paramétrico no debe saber nada de los argumentos correspondientes pero tratarlos de igual forma, porque pueden adoptar a cualquier tipo. Esto es bastante restrictivo, porque dicha función solo puede trabajar con aquellas propiedades de sus argumentos que no son parte de sus datos:
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"]);
Polimorfismo ad-hoc (también conocido como sobrecarga)
El polimorfismo ad-hoc dice que los diferentes tipos son equivalentes solo para un propósito específico. Para ser equivalente en este sentido, un tipo debe implementar un conjunto de funciones específicas para ese propósito. Una función que define uno o más parámetros de tipo polimórfico ad-hoc necesita saber qué conjuntos de funciones están asociados a cada uno de sus argumentos.
El polimorfismo ad-hoc hace que una función sea compatible con un dominio de tipos más amplio. El siguiente ejemplo ilustra el propósito de la "asignación" y cómo los tipos pueden implementar esta restricción. En lugar de un conjunto de funciones, la restricción "asignable" solo incluye una única map
función:
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)
);
Polimorfismo de subtipo
Dado que otras respuestas ya cubren el polimorfismo de subtipo, lo omito.
Polimorfismo estructural (también conocido como subtipo estructural)
El polimorfismo estructural dice que diferentes tipos son equivalentes, si contienen la misma estructura de tal manera que un tipo tiene todas las propiedades del otro pero puede incluir propiedades adicionales. Dicho esto, el polimorfismo estructural consiste en escribir pato en tiempo de compilación y ciertamente ofrece cierta seguridad de tipo adicional. Pero al afirmar que dos valores son del mismo tipo solo porque comparten algunas propiedades, ignora por completo el nivel semántico de valores:
const weight = {value: 90, foo: true};
const speed = {value: 90, foo: false, bar: [1, 2, 3]};
Desafortunadamente, speed
se considera un subtipo de weight
y tan pronto como comparamos las value
propiedades, estamos comparando virtualmente manzanas con naranjas.