Haskell Language
Type de familles
Recherche…
Type Synonyme Familles
Les familles de types synonyme ne sont que des fonctions de type: elles associent les types de paramètres aux types de résultats. Ceux-ci viennent dans trois variétés différentes.
Familles de type synonyme fermé
Celles-ci fonctionnent beaucoup comme les fonctions Haskell ordinaires au niveau de la valeur: vous spécifiez des clauses, en mappant certains types à d'autres:
{-# LANGUAGE TypeFamilies #-}
type family Vanquisher a where
Vanquisher Rock = Paper
Vanquisher Paper = Scissors
Vanquisher Scissors = Rock
data Rock=Rock; data Paper=Paper; data Scissors=Scissors
Familles ouvertes de type synonyme
Celles-ci fonctionnent plus comme des instances de type typeclass: tout le monde peut ajouter d'autres clauses dans d'autres modules.
type family DoubledSize w
type instance DoubledSize Word16 = Word32
type instance DoubledSize Word32 = Word64
-- Other instances might appear in other modules, but two instances cannot overlap
-- in a way that would produce different results.
Synonymes de type associé à la classe
Une famille de type ouvert peut également être combinée avec une classe réelle. Cela se fait généralement lorsque, comme avec les familles de données associées , une méthode de classe nécessite des objets auxiliaires supplémentaires, et que ces objets peuvent être différents pour différentes instances, mais peuvent également être partagés. Un bon exemple est la classe VectorSpace
:
class VectorSpace v where
type Scalar v :: *
(*^) :: Scalar v -> v -> v
instance VectorSpace Double where
type Scalar Double = Double
μ *^ n = μ * n
instance VectorSpace (Double,Double) where
type Scalar (Double,Double) = Double
μ *^ (n,m) = (μ*n, μ*m)
instance VectorSpace (Complex Double) where
type Scalar (Complex Double) = Complex Double
μ *^ n = μ*n
Notez que dans les deux premières instances, l'implémentation de Scalar
est la même. Cela ne serait pas possible avec une famille de données associée: les familles de données sont injectives , les familles de type synonyme ne le sont pas.
Bien que la non-injectivité ouvre certaines possibilités comme ci-dessus, cela rend aussi l'inférence de type plus difficile. Par exemple, ce qui suit ne sera pas typique:
class Foo a where
type Bar a :: *
bar :: a -> Bar a
instance Foo Int where
type Bar Int = String
bar = show
instance Foo Double where
type Bar Double = Bool
bar = (>0)
main = putStrLn (bar 1)
Dans ce cas, le compilateur ne peut pas savoir quelle instance utiliser, car l'argument de bar
est lui-même un littéral polymorphe Num
. Et la fonction de type Bar
ne peut pas être résolue en «sens inverse», précisément parce qu'elle n'est pas injective † et n'est donc pas inversible (il peut y avoir plus d'un type avec Bar a = String
).
† Avec seulement ces deux instances, il est en réalité injectif, mais le compilateur ne peut pas savoir que quelqu'un ajoutera plus d'instances ultérieurement et rompra ainsi le comportement.
Familles de types de données
Les familles de données peuvent être utilisées pour créer des types de données ayant des implémentations différentes basées sur leurs arguments de type.
Familles de données autonomes
{-# LANGUAGE TypeFamilies #-}
data family List a
data instance List Char = Nil | Cons Char (List Char)
data instance List () = UnitList Int
Dans la déclaration ci-dessus, Nil :: List Char
et UnitList :: Int -> List ()
Familles de données associées
Les familles de données peuvent également être associées à des classes de caractères. Ceci est souvent utile pour les types avec des «objets auxiliaires», qui sont requis pour les méthodes génériques de typeclass mais qui doivent contenir des informations différentes selon l'instance concrète. Par exemple, l'indexation des emplacements dans une liste ne nécessite qu'un seul numéro, tandis que dans un arbre, vous avez besoin d'un numéro pour indiquer le chemin d'accès à chaque noeud:
class Container f where
data Location f
get :: Location f -> f a -> Maybe a
instance Container [] where
data Location [] = ListLoc Int
get (ListLoc i) xs
| i < length xs = Just $ xs!!i
| otherwise = Nothing
instance Container Tree where
data Location Tree = ThisNode | NodePath Int (Location Tree)
get ThisNode (Node x _) = Just x
get (NodePath i path) (Node _ sfo) = get path =<< get i sfo
L'injectivité
Type Les familles ne sont pas nécessairement injectables. Par conséquent, nous ne pouvons pas déduire le paramètre d'une application. Par exemple, dans servant
, étant donné un type Server a
nous ne pouvons pas déduire le type a
. Pour résoudre ce problème, nous pouvons utiliser Proxy
. Par exemple, en servant
, le serve
la fonction est de type ... Proxy a -> Server a -> ...
. Nous pouvons en déduire a
de Proxy a
car Proxy
est défini par des data
qui sont injectives.