Creo que he pedido esto en Haskell-Cafe en algún momento, pero maldita sea si puedo encontrar la respuesta ahora ... Así que lo vuelvo a pedir aquí, así que espero que en el futuro pueda encontrar la respuesta!Tipos asociados y elementos de contenedor
Haskell es fantástico en el tratamiento del polimorfismo paramétrico. Pero el problema es que no todo es paramétrico. Como ejemplo trivial, supongamos que queremos extraer el primer elemento de datos de un contenedor. Para un tipo paramétrico, eso es trivial:
class HasFirst c where first :: c x -> Maybe x instance HasFirst [] where first [] = Nothing first (x:_) = Just x
Ahora tratar de escribir una instancia para ByteString
. No puedes. Su tipo no menciona el tipo de elemento. Tampoco puede escribir una instancia para Set
, porque requiere una restricción Ord
, pero el encabezado de clase no menciona el tipo de elemento, por lo que no puede restringirlo.
tipos asociados proporcionan una clara forma de solucionar estos problemas por completo:
class HasFirst c where type Element c :: * first :: c -> Maybe (Element c) instance HasFirst [x] where type Element [x] = x first [] = Nothing first (x:_) = Just x instance HasFirst ByteString where type Element ByteString = Word8 first b = b ! 0 instance Ord x => HasFirst (Set x) where type Element (Set x) = x first s = findMin s
Ahora tenemos un nuevo problema, sin embargo. Considere la posibilidad de tratar de "arreglar" Functor
para que funcione para todos los tipos de contenedores:
class Functor f where type Element f :: * fmap :: (Functor f2) => (Element f -> Element f2) -> f -> f2
Esto no funciona en absoluto. Dice que si tenemos una función del tipo de elemento de f
para el tipo de elemento de f2
, podemos convertir un f
en un f2
. Hasta aquí todo bien. Sin embargo, aparentemente hay de ninguna manera para exigir que f
y f2
sean el mismo tipo de contenedor.
Bajo el Functor
definición existente, tenemos
fmap :: (x -> y) -> [x] -> [y] fmap :: (x -> y) -> Seq x -> Seq y fmap :: (x -> y) -> IO x -> IO y
Pero tenemos no tienen fmap :: (x -> y) -> IO x -> [y]
. Eso es completamente imposible. Pero la definición de clase anterior lo permite.
¿Alguien sabe cómo explicarle al sistema de tipo qué en realidad significa?
Editar
Los trabajos anteriores mediante la definición de una forma de calcular un tipo de elemento de un tipo de contenedor. ¿Qué pasa si tratas de hacerlo al revés? ¿Define una función para calcular un tipo de contenedor a partir de un tipo de elemento? ¿Funciona eso más fácil?
Todavía estoy luchando para comprender exactamente qué está pasando aquí ... Lógicamente, es simple. Queremos una función de mapa que acepte cualquier tipo de entrada legal y que produzca cualquier tipo de salida legal. La parte difícil es definir qué tipos son "legales" para un contenedor dado. (No me gusta esta distinción "rígida" frente a "paramétrica"). ¿Alguien puede explicar lo que significan todos los "~" caracteres? ¿O qué significa "Restricción"? – MathematicalOrchid
He ampliado mi respuesta para poder explicar mejor las cosas; También recomendaría leer la publicación del blog que he vinculado para obtener una explicación más detallada de "Restricción". – ehird
Así que tipo "*" es un tipo normal, tipo "* -> *" es cualquier constructor de tipo, y el tipo "Restricción" no es un tipo en absoluto, es una restricción de tipo? ¿Está bien? – MathematicalOrchid