Comment fonctionne la dérivation dans Haskell?

Les types de données algébriques (ADT) dans Haskell peuvent automatiquement devenir des instances de certaines typeclasse s (comme Show , Eq ) en dérivant d’eux.

 data Maybe a = Nothing | Just a deriving (Eq, Ord) 

Ma question est la suivante: comment cela fonctionne-t-il, c’est-à-dire comment Haskell sait-il comment mettre en œuvre les fonctions de la classe de types dérivée pour l’ADT dérivant?

Aussi, pourquoi la deriving est-elle limitée à certaines classes de classes seulement? Pourquoi ne puis-je pas écrire ma propre classe de caractères pouvant être dérivée?

La réponse courte est magique :-). Cela signifie que la dérivation automatique est intégrée à la spécification Haskell et que chaque compilateur peut choisir de l’implémenter à sa manière. Il y a cependant beaucoup de travail à faire pour le rendre extensible.

Dériver est un outil pour Haskell pour vous permettre d’écrire vos propres mécanismes de déduction.

GHC fournissait une extension de classe de type dérivable appelée Generic Classes , mais elle était rarement utilisée, car elle était plutôt faible. Cela a été retiré et des travaux sont en cours pour intégrer un nouveau mécanisme de dérivation générique, tel que décrit dans le présent document: http://www.dreixel.net/research/pdf/gdmh.pdf

Pour plus d’informations, voir:

Extrait du rapport Haskell 98:

Les seules classes du Prélude pour lesquelles des instances dérivées sont autorisées sont Eq, Ord, Enum, Bounded, Show et Read …

Voici la description de la façon de dériver ces classes de type: http://www.haskell.org/onlinereport/derived.html#derived-appendix

Il est possible d’utiliser Template Haskell pour générer des déclarations d’instance de la même manière que les clauses de dérivation.

L’exemple suivant est volé sans vergogne dans le wiki Haskell :

Dans cet exemple, nous utilisons le code Haskell suivant

 $(gen_render ''Body) 

pour produire l’instance suivante:

 instance TH_Render Body where render (NormalB exp) = build 'normalB exp render (GuardedB guards) = build 'guardedB guards 

La fonction gen_render ci-dessus est définie comme suit. (Notez que ce code doit être dans un module séparé de l’utilisation ci-dessus).

 -- Generate an intance of the class TH_Render for the type typName gen_render :: Name -> Q [Dec] gen_render typName = do (TyConI d) <- reify typName -- Get all the information on the type (type_name,_,_,constructors) <- typeInfo (return d) -- extract name and constructors i_dec <- gen_instance (mkName "TH_Render") (conT type_name) constructors -- generation function for method "render" [(mkName "render", gen_render)] return [i_dec] -- return the instance declaration -- function to generation the function body for a particular function -- and constructor where gen_render (conName, components) vars -- function name is based on constructor name = let funcName = makeName $ unCapalize $ nameBase conName -- choose the correct builder function headFunc = case vars of [] -> "func_out" otherwise -> "build" -- build 'funcName parm1 parm2 parm3 ... in appsE $ (varE $ mkName headFunc):funcName:vars -- put it all together -- equivalent to 'funcStr where funcStr CONTAINS the name to be returned makeName funcStr = (appE (varE (mkName "mkName")) (litE $ SsortingngL funcStr)) 

Qui utilise les fonctions et types suivants.

Tout d’abord, tapez des synonymes pour rendre le code plus lisible.

 type Constructor = (Name, [(Maybe Name, Type)]) -- the list of constructors type Cons_vars = [ExpQ] -- A list of variables that bind in the constructor type Function_body = ExpQ type Gen_func = Constructor -> Cons_vars -> Function_body type Func_name = Name -- The name of the instance function we will be creating -- For each function in the instance we provide a generator function -- to generate the function body (the body is generated for each constructor) type Funcs = [(Func_name, Gen_func)] 

La principale fonction réutilisable. Nous passons la liste des fonctions pour générer les fonctions de l’instance.

 -- construct an instance of class class_name for type for_type -- funcs is a list of instance method names with a corresponding -- function to build the method body gen_instance :: Name -> TypeQ -> [Constructor] -> Funcs -> DecQ gen_instance class_name for_type constructors funcs = instanceD (cxt []) (appT (conT class_name) for_type) (map func_def funcs) where func_def (func_name, gen_func) = funD func_name -- method name -- generate function body for each constructor (map (gen_clause gen_func) constructors) 

Une fonction d’assistance de ce qui précède.

 -- Generate the pattern match and function body for a given method and -- a given constructor. func_body is a function that generations the -- function body gen_clause :: (Constructor -> [ExpQ] -> ExpQ) -> Constructor -> ClauseQ gen_clause func_body data_con@(con_name, components) = -- create a parameter for each component of the constructor do vars <- mapM var components -- function (unnamed) that pattern matches the constructor -- mapping each component to a value. (clause [(conP con_name (map varP vars))] (normalB (func_body data_con (map varE vars))) []) -- create a unique name for each component. where var (_, typ) = newName $ case typ of (ConT name) -> toL $ nameBase name otherwise -> "parm" where toL (x:y) = (toLower x):y unCapalize :: [Char] -> [Char] unCapalize (x:y) = (toLower x):y 

Et un code d’aide emprunté à Syb III / replib 0.2.

 typeInfo :: DecQ -> Q (Name, [Name], [(Name, Int)], [(Name, [(Maybe Name, Type)])]) typeInfo m = do d <- m case d of d@(DataD _ _ _ _ _) -> return $ (simpleName $ name d, paramsA d, consA d, termsA d) d@(NewtypeD _ _ _ _ _) -> return $ (simpleName $ name d, paramsA d, consA d, termsA d) _ -> error ("derive: not a data type declaration: " ++ show d) where consA (DataD _ _ _ cs _) = map conA cs consA (NewtypeD _ _ _ c _) = [ conA c ] {- This part no longer works on 7.6.3 paramsA (DataD _ _ ps _ _) = ps paramsA (NewtypeD _ _ ps _ _) = ps -} -- Use this on more recent GHC rather than the above paramsA (DataD _ _ ps _ _) = map nameFromTyVar ps paramsA (NewtypeD _ _ ps _ _) = map nameFromTyVar ps nameFromTyVar (PlainTV a) = a nameFromTyVar (KindedTV a _) = a termsA (DataD _ _ _ cs _) = map termA cs termsA (NewtypeD _ _ _ c _) = [ termA c ] termA (NormalC c xs) = (c, map (\x -> (Nothing, snd x)) xs) termA (RecC c xs) = (c, map (\(n, _, t) -> (Just $ simpleName n, t)) xs) termA (InfixC t1 c t2) = (c, [(Nothing, snd t1), (Nothing, snd t2)]) conA (NormalC c xs) = (simpleName c, length xs) conA (RecC c xs) = (simpleName c, length xs) conA (InfixC _ c _) = (simpleName c, 2) name (DataD _ n _ _ _) = n name (NewtypeD _ n _ _ _) = n name d = error $ show d simpleName :: Name -> Name simpleName nm = let s = nameBase nm in case dropWhile (/=':') s of [] -> mkName s _:[] -> mkName s _:t -> mkName t