Algunos lenguajes permiten clases y funciones con parámetros de tipo (como List<T>
donde T
puede ser un tipo arbitrario). Por ejemplo, puede tener una función como:
List<S> Function<S, T>(List<T> list)
Sin embargo, algunos idiomas permiten que este concepto se extienda un nivel más alto, lo que le permite tener una función con la firma:
K<S> Function<K<_>, S, T>(K<T> arg)
Donde K<_>
sí mismo es un tipo como List<_>
ese tiene un parámetro de tipo. Este "tipo parcial" se conoce como constructor de tipos.
Mi pregunta es, ¿por qué necesitas esta habilidad? Tiene sentido tener un tipo como List<T>
porque todos List<T>
son casi exactamente iguales, pero todos K<_>
pueden ser completamente diferentes. Puede tener un Option<_>
y un List<_>
que no tienen una funcionalidad común en absoluto.
Functor
ejemplo en la respuesta de Luis Casillas es bastante intuitivo. ¿Qué hacenList<T>
yOption<T>
tienen en común? Si me das uno y una funciónT -> S
, puedo darte unList<S>
oOption<S>
. Otra cosa que tienen en común es que puede intentar obtener unT
valor de ambos.IReadableHolder<T>
.IMappable<K<_>, T>
el métodoK<S> Map(Func<T, S> f)
, como la implementaciónIMappable<Option<_>, T>
,IMappable<List<_>, T>
. Entonces tendrías que restringirK<T> : IMappable<K<_>, T>
para sacarle provecho.Respuestas:
Como nadie más ha respondido la pregunta, creo que lo intentaré yo mismo. Voy a tener que ponerme un poco filosófico.
La programación genérica se trata de abstraer sobre tipos similares, sin la pérdida de información de tipo (que es lo que sucede con el polimorfismo de valor orientado a objetos). Para hacer esto, los tipos necesariamente deben compartir algún tipo de interfaz (un conjunto de operaciones, no el término OO) que pueda usar.
En lenguajes orientados a objetos, los tipos satisfacen una interfaz en virtud de las clases. Cada clase tiene su propia interfaz, definida como parte de su tipo. Como todas las clases
List<T>
comparten la misma interfaz, puede escribir código que funcione sin importar cuálT
elija. Otra forma de imponer una interfaz es una restricción de herencia, y aunque las dos parecen diferentes, son similares si lo piensas.En la mayoría de los lenguajes orientados a objetos,
List<>
no es un tipo adecuado en sí mismo. No tiene métodos y, por lo tanto, no tiene interfaz. Es soloList<T>
que tiene estas cosas. Esencialmente, en términos más técnicos, los únicos tipos sobre los que puede abstraerse significativamente son aquellos con el tipo*
. Para utilizar tipos de tipo superior en un mundo orientado a objetos, debe expresar las restricciones de tipo de manera coherente con esta restricción.Por ejemplo, como se menciona en los comentarios, podemos ver
Option<>
yList<>
"mapear", en el sentido de que si tiene una función, puede convertir unaOption<T>
en unaOption<S>
, o unaList<T>
en unaList<S>
. Recordando que las clases no pueden usarse para abstraer directamente sobre tipos de tipo superior, en su lugar hacemos una interfaz:Y luego implementamos la interfaz en ambos
List<T>
yOption<T>
comoIMappable<List<_>, T>
yIMappable<Option<_>, T>
respectivamente. Lo que hemos hecho es usar tipos de tipo superior para colocar restricciones en los tipos reales (no de tipo superior)Option<T>
yList<T>
. Así es como se hace en Scala, aunque, por supuesto, Scala tiene características tales como rasgos, variables de tipo y parámetros implícitos que lo hacen más expresivo.En otros idiomas, es posible abstraer directamente sobre tipos de tipo superior. En Haskell, una de las máximas autoridades en sistemas de tipos, podemos formular una clase de tipo para cualquier tipo, incluso si tiene un tipo superior. Por ejemplo,
Esta es una restricción colocada directamente en un tipo (no especificado)
mp
que toma un parámetro de tipo y requiere que esté asociado con la funciónmap
que convierte unmp<a>
en unmp<b>
. Luego podemos escribir funciones que restrinjan los tipos de tipo superiorMappable
al igual que en los lenguajes orientados a objetos, podría colocar una restricción de herencia. Especie de.Para resumir, su capacidad para hacer uso de tipos de tipo superior depende de su capacidad para restringirlos o usarlos como parte de las restricciones de tipo.
fuente
(Mappable mp) => mp a -> mp b
, ha colocado una restricciónmp
para ser miembro de la clase de tipoMappable
. Cuando declara un tipo comoOption
una instancia deMappable
, agrega comportamiento a ese tipo. Supongo que podría hacer uso de ese comportamiento localmente sin restringir ningún tipo, pero no es diferente de definir una función ordinaria.*
sin dejarlos inutilizables. Sin embargo, es definitivamente cierto que las clases de tipos son muy poderosas cuando se trabaja con tipos de tipo superior.