Una extensión que permite definir funciones sobre tipos.
Activa varias formas de definir estas:
3 posibilidades
La anotación de kind es opcional y por defecto es *
:
Definiendo el conjunto completo de equaciones de la familia
En GHCi:
*Main> :t (undefined :: F Int)
(undefined :: F Int) :: Double
*Main> :t (undefined :: F [a])
(undefined :: F [a]) :: String
type family F a :: *
type instance F [Int] = Int -- OK!
type instance F String = Char -- OK!
type instance F (F a) = a -- WRONG: type parameter mentions a type family
type instance
F (forall a. (a, b)) = b -- WRONG: a forall type appears in a type parameter
type instance
F Float = forall a.a -- WRONG: right-hand side may not be a forall type
type family H a where -- OK!
H Int = Int
H Bool = Bool
H a = String
type instance H Char = Char -- WRONG: cannot have instances of closed family
type family K a where -- OK!
type family G a b :: * -> *
type instance G Int = (,) -- WRONG: must be two type parameters
type instance G Int Char Float = Double -- WRONG: must be two type parameters
La reescritura bajo type families tiene reglas estrictas para ser convergente:
type instance F (a, Int) = [a]
type instance F (Int, b) = [b] -- overlap permitted
type instance G (a, Int) = [a]
type instance G (Char, a) = [a] -- ILLEGAL overlap, as [Char] /= [Int]
type family F a where
F Int = Bool --
F a = Char -- dos ecuaciones incompatibles
type family G a where
G Int = Int --
G a = a -- dos ecuaciones compatibles
> :t (undefined :: G a)
(undefined :: G a) :: a
> :t (undefined :: F Int)
(undefined :: F Int) :: Bool
> :t (undefined :: F [Int])
(undefined :: F [Int]) :: Char
> :t (undefined :: F a)
(undefined :: F a) :: a
<interactive> error:
• Couldn't match expected type ‘F a’ with actual type ‘F a0’
NB: ‘F’ is a type function, and may not be injective
The type variable ‘a0’ is ambiguous
• In the ambiguity check for an expression type signature
To defer the ambiguity check to use sites, enable AllowAmbiguousTypes
In an expression type signature: F a
In the expression: (undefined :: F a)
family
instance
Las instancias de tipos que se corresponden con los parametros de clases deben tener precisamente el mismo tipo dado en la intancia de la clase:
class Collects ce where
type Elem ce :: *
instance Eq (Elem [e]) => Collects [e] where
-- Choose one of the following alternatives:
type Elem [e] = e -- OK
type Elem [x] = x -- BAD; '[x]' is different to '[e]' from head
type Elem x = x -- BAD; 'x' is different to '[e]'
type Elem [Maybe x] = x -- BAD: '[Maybe x]' is different to '[e]'
Caso donde la inferencia falla en presencia de type families:
type family Id a
type instance Id Int = Int
type instance Id Bool = Bool
id :: Id t -> Id t
id x = x
Se rechaza la definición de id
por que la variable t
aparece solamente bajo aplicaciones de familias de tipos, y es ambigua.
Con la extensión TypeFamilyDependencies
:
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
class Monad m => Store store m | store -> m where
new :: a -> m (store a)
get :: store a -> m a
put :: store a -> a -> m ()
class Store store where
type StoreMonad store :: * -> *
new :: a -> (StoreMonad store) (store a)
get :: store a -> (StoreMonad store) a
put :: store a -> a -> (StoreMonad store) ()
storeStrings :: (Store store, Monad (StoreMonad store))
=> [String] -> (StoreMonad store) (store [String])
storeStrings xs = do
store <- new []
forM_ xs $ \x -> do
old <- get store
put store (x : old)
return store
Las type families proveen un estilo de programación sobre tipo más funcional mientras que las dependencias funciones proveen un estilo de programación relacional.
Además usar type families permite evitar de activar extensiones como:
https://wiki.haskell.org/Functional_dependencies_vs._type_families
GADTSyntax
La extensión GADTSyntax
permite definir tipos de datos algebraicos de la forma siguiente:
En la definición siguiente, la x
es un tipo existencial:
Usando el constructor Exists
, el tipo x
es escondido dentro del tipo Exists
. Podemos por ejemplo poner distintos tipos en una lista:
Pero luego se pierden esos tipos, no podemos hacer patten matching. . . .
¿Porqué el forall
? Tiene sentido si lo vemos con la sintaxis GADTSyntax
:
Exists
es una función polimórfica, cualquier funcion polimórfica tiene forall
implicitos:
Para poder escribir los forall
explicitos activar la extensión ExplicitForAll
.
Esta función tiene polimorfismo de rango 2 (tiene un argumento polimórfico). Requiere la extensión RankNTypes
(implica ExplicitForAll
).
Un tipo fantasma es un tipo que no tiene un valor asociado, como phantom
en lo que sigue:
La variable phantom
no tiene un valor asociado a ella en la parte a la derecha del símbolo igual. Esto significa que cuando construimos un valor de tipo P
, podemos también darle un tipo para phantom
. Como phantom
no tiene un valor que le corresponde, es libre de unificarse con cualquier cosa en el sistema de tipos.
Por ejemplo, cada uno de los siguientes casos es válido, incluso dentro de un mismo programa:
Podríamos imaginar estos ejemplos como formas de anotar el valor P 5
. En otros términos, una aplicación de los tipos fantasmas, es que nos permiten embedir más información en nuestros tipos. En particular, queremos adjuntar evidencia, o pruevas, a nuestros tipos. Es decir, queremos asociar el tipo fantasma con un tipo testigo.
GADTs
Generalized Algebraic Data Types:
Ejemplo: definir un tipo “expresión” y un evaluador
https://www.cis.upenn.edu/~sweirich/talks/GADT.pdf
Combinados con tipos fantasmas, se pueden usar para agregar información al nivel de los tipos para garantizar propiedades:
data Empty
data NonEmpty
data SafeList a b where
Nil :: SafeList a Empty
Cons :: a -> SafeList a b -> SafeList a NonEmpty
¿Definición?
Ver:
Comparación type families vs. functional dependencies: