La palabra "polimorfismo" puede tener diferentes significados dependiendo del contexto. Cuando los programadores hablan de polimorfismo en lenguajes imperativos, generalmente se refieren al "polimorfismo de subtipos". Por otro lado, los programadores en lenguajes funcionales se refieren al "polimorfismo paramétrico". Vamos a hablar sobre este último.
En esta lección aparece código en TypeScript. No te preocupes si no lo entiendes completamente. Nuestro objetivo es comprender los conceptos, no TypeScript.
En la biblioteca lodash, hay una función llamada _.concat() que combina las matrices (arrays) que se le pasan:
_.concat([1], [2, 3, 1]); // [1, 2, 3, 1]
_.concat(['one'], ['two', 'three']); // ['one', 'two', 'three']
_.concat([true], [false, false, true]); // [true, false, false, true]
Esta función combina cualquier tipo de array, independientemente del tipo de datos que contengan. Intentemos implementarla nosotros mismos.
// Esta es una versión simplificada de la función concat, solo funciona con dos argumentos,
// ambos son arrays
// La función crea un nuevo array, luego recorre los arrays pasados uno por uno
// y agrega sus valores al nuevo array. Luego se devuelve.
const concat = (coll1, coll2) => {
const result = [];
coll1.forEach((value) => result.push(value));
coll2.forEach((value) => result.push(value));
return result;
};
Observa detenidamente este código. ¿Se realizan operaciones en los datos dentro del array? La respuesta correcta es no. Estos datos se transfieren de un array a otro, pero no se realiza ninguna operación sobre ellos. Nuestra nueva función concat(), al igual que la original _.concat(), puede trabajar con arrays que contienen cualquier tipo de datos.
Para los desarrolladores que solo han trabajado con lenguajes dinámicos, este comportamiento puede parecer natural, pero en los lenguajes estáticos no es tan sencillo.
const ages = [34, 18, 6, 60, 25];
const words = ['one', 'two', 'three'];
En TypeScript, al crear un array, se especifica el tipo de los elementos de ese array. Para el primero es number, para el segundo es string. Agregar un elemento de un tipo diferente al array resultará en un error:
// Se agrega una cadena a un array de números
ages.push('hello'); // Error
Las funciones que operan en arrays esperan un tipo específico:
const concat = (arr1: number[], arr2: number[]): number[] => {
// Crea un nuevo array con una longitud igual a la suma de las longitudes de los arrays originales
const result: number[] = [];
// Transfiere todos los valores del primer array a result
for (let i = 0; i < arr1.length; i += 1) {
result[i] = arr1[i];
}
// Transfiere todos los valores del segundo array a result
for (let j = 0; j < arr2.length; j += 1) {
result[arr1.length + j] = arr2[j];
}
return result;
};
// Declaración del array a
const a = [1, 2, 3, 4];
// Declaración del array b
const b = [4, 16, 1, 2, 3, 22];
// Combina los arrays
const result = concat(a, b);
console.log(result); // => [1, 2, 3, 4, 4, 16, 1, 2, 3, 22]
Observa la firma de la función concat(): (arr1: number[], arr2: number[]): number[]. A diferencia de la versión en JavaScript, aquí se especifica que los parámetros de entrada son arrays de números number[]. Esto significa que la función no funcionará para arrays de strings ni para ningún otro tipo de datos.
¿Qué significa esto en la práctica? Una cosa muy simple y triste. Tendremos que implementar una función similar para cada tipo a pesar de que el algoritmo interno es completamente idéntico.
Aquí es donde entra en juego el polimorfismo paramétrico. Los lenguajes estáticos tienen que introducir construcciones especiales en el lenguaje que permitan describir algoritmos similares sin importar el tipo del parámetro. En algunos lenguajes se llaman plantillas (C++) o genéricos (TypeScript, Java, C#):
const concat = <T>(arr1: T[], arr2: T[]): T[] => {
const result: T[] = [];
for (let i = 0; i < arr1.length; i += 1) {
result[i] = arr1[i];
}
for (let j = 0; j < arr2.length; j += 1) {
result[arr1.length + j] = arr2[j];
}
return result;
};
const a = [1, 2, 3, 4];
const b = [4, 16, 1, 2, 3, 22];
const result = concat(a, b);
// Llamada a la función con el tipo number[]
console.log(result); // => [ 1, 2, 3, 4, 4, 16, 1, 2, 3, 22 ]
const words1 = ['one', 'two', 'three'];
const words2 = ['four', 'five'];
// Llamada a la función con el tipo string[]
const wordsResult = concat(words1, words2);
console.log(wordsResult); // => [ 'one', 'two', 'three', 'four', 'five' ]
En este código, aparece el tipo T, que precisamente permite utilizar cualquier tipo dentro del array. Ahora el método concat() funciona de manera similar a su contraparte en JavaScript.
El polimorfismo paramétrico permite escribir algoritmos genéricos para tipos compuestos, lo que en algunos casos reduce significativamente la cantidad de código. A veces, esto puede complicar la solución, pero para la mayoría de las operaciones típicas, la complejidad no aumenta mucho. Esto se puede ver en el código anterior.
En los lenguajes dinámicos, no se necesita polimorfismo paramétrico para implementar algoritmos genéricos. Cualquier colección puede contener cualquier tipo de datos en cualquier momento. Gracias a esto, no es necesario introducir construcciones adicionales en el lenguaje ni aprender nuevos conceptos.
En la literatura, el uso del polimorfismo paramétrico a menudo se denomina programación genérica.
Para acceder completo a curso necesitas un plan básico
El plan básico te dará acceso completo a todos los cursos, ejercicios y lecciones de Códica, proyectos y acceso de por vida a la teoría de las lecciones completadas. La suscripción se puede cancelar en cualquier momento.