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ál T
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 solo List<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<>
y List<>
"mapear", en el sentido de que si tiene una función, puede convertir una Option<T>
en una Option<S>
, o una List<T>
en una List<S>
. Recordando que las clases no pueden usarse para abstraer directamente sobre tipos de tipo superior, en su lugar hacemos una interfaz:
IMappable<K<_>, T> where K<T> : IMappable<K<_>, T>
Y luego implementamos la interfaz en ambos List<T>
yOption<T>
como IMappable<List<_>, T>
y IMappable<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>
y List<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,
class Mappable mp where
map :: mp a -> mp b
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ón map
que convierte unmp<a>
en un mp<b>
. Luego podemos escribir funciones que restrinjan los tipos de tipo superior Mappable
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.