¿Por qué necesitas tipos más altos?

13

Algunos lenguajes permiten clases y funciones con parámetros de tipo (como List<T>donde Tpuede 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.

GregRos
fuente
77
Hay varias buenas respuestas aquí: stackoverflow.com/questions/21170493/…
itsbruce
3
@itsbruce En particular, el Functorejemplo en la respuesta de Luis Casillas es bastante intuitivo. ¿Qué hacen List<T>y Option<T>tienen en común? Si me das uno y una función T -> S, puedo darte un List<S>o Option<S>. Otra cosa que tienen en común es que puede intentar obtener un Tvalor de ambos.
Doval
@Doval: ¿Cómo harías lo primero? En la medida en que uno esté interesado en lo último, creo que podría manejarse con la implementación de ambos tipos IReadableHolder<T>.
supercat
@supercat que supongo que tendrían que poner en práctica IMappable<K<_>, T>el método K<S> Map(Func<T, S> f), como la implementación IMappable<Option<_>, T>, IMappable<List<_>, T>. Entonces tendrías que restringir K<T> : IMappable<K<_>, T>para sacarle provecho.
GregRos
1
El casting no es una analogía apropiada. Lo que se hace con las clases de tipos (o construcciones similares) es que muestra cómo un tipo determinado satisface el tipo de tipo superior. Esto generalmente implica definir nuevas funciones o mostrar qué funciones existentes (o métodos si es un lenguaje OO como Scala) se pueden usar. Una vez hecho esto, todas las funciones definidas para trabajar con el tipo superior funcionarán con ese tipo. Pero es mucho más que una interfaz porque se define más que un simple conjunto de firmas de función / método. Creo que tendré que ir y escribir una respuesta para mostrar cómo funciona.
itsbruce

Respuestas:

5

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 Telija. 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) mpque toma un parámetro de tipo y requiere que esté asociado con la función mapque convierte unmp<a> en un mp<b>. Luego podemos escribir funciones que restrinjan los tipos de tipo superior Mappableal 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.

GregRos
fuente
He estado demasiado ocupado cambiando de trabajo, pero finalmente encontraré tiempo para escribir mi propia respuesta. Sin embargo, creo que te ha distraído la idea de las restricciones. Un aspecto significativo de los tipos de tipo superior es que permiten definir funciones / comportamientos que se pueden aplicar a cualquier tipo que lógicamente pueda ser calificado. Lejos de imponer restricciones a los tipos existentes, las clases de tipos (una aplicación de tipos de tipo superior) les agregan comportamiento.
itsbruce
Creo que es una cuestión de semántica. Una clase de tipo define una clase de tipos. Cuando define una función como (Mappable mp) => mp a -> mp b, ha colocado una restricción mppara ser miembro de la clase de tipo Mappable. Cuando declara un tipo como Optionuna instancia de Mappable, 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.
GregRos
Además, estoy bastante seguro de que las clases de tipos no están directamente relacionadas con los tipos de tipo superior. Scala tiene tipos de tipo superior, pero no clases de tipos, y podría restringir las clases de tipos a tipos con el tipo *sin dejarlos inutilizables. Sin embargo, es definitivamente cierto que las clases de tipos son muy poderosas cuando se trabaja con tipos de tipo superior.
GregRos
Scala tiene clases de tipo. Se implementan con implicidades. Las implícitas proporcionan el equivalente de las instancias de clase de tipo Haskell. Es una implementación más frágil que la de Haskell porque no tiene una sintaxis dedicada. Pero siempre fue uno de los propósitos clave de Scala implicits y hacen el trabajo.
itsbruce