2010-10-05 20 views
79

Algebraic Tipos de datos (ADT) en Haskell pueden convertirse automáticamente los casos de algunos typeclasse s (como Show, Eq) por derivadas de ellos.¿Cómo funciona la derivación en Haskell?

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

Mi pregunta es, ¿cómo afecta esto deriving trabajo, es decir, ¿cómo Haskell sabe cómo poner en práctica las funciones de la clase de tipos derivada para el ADT derivar?

Además, ¿por qué deriving está restringido solo a ciertas clases de tipos? ¿Por qué no puedo escribir mi propia clase de tipos que se puede derivar?

Respuesta

59

La respuesta corta es, magia :-). Esto quiere decir que la derivación automática está integrada en la especificación de Haskell, y cada compilador puede elegir implementarla a su manera. Sin embargo, hay mucho trabajo sobre cómo hacerlo extensible.

Derive es una herramienta para que Haskell le permita escribir sus propios mecanismos de derivación.

GHC solía proporcionar una extensión de clase de tipo derivable llamada Generic Classes, pero rara vez se utilizaba, ya que era algo débil. que ahora se ha llevado a cabo, y se está trabajando para integrar un nuevo mecanismo que se derivan genérica como se describe en este documento: http://www.dreixel.net/research/pdf/gdmh.pdf

Para más información sobre este tema, consulte:

+1

Ver también 'StandaloneDeriving' en [el manual de GHC] (http://www.haskell.org/ghc/docs/latest/html/users_guide/deriving.html#stand-alone-deriving) y [haskellwiki] (http://www.haskell.org/haskellwiki/GHC/Stand-alone_deriving_declarations) – AndrewC

2

es posible utilizar Template Haskell para generar declaraciones de instancia de forma similar a derivar-cláusulas.

El siguiente ejemplo es robado sin pudor de la Haskell Wiki:

En este ejemplo se utiliza el siguiente código Haskell

$(gen_render ''Body) 

para producir el siguiente ejemplo:

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

El La función gen_render anterior se define de la siguiente manera. (Tenga en cuenta que este código debe estar en un módulo separado del uso anterior).

-- 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 $ StringL funcStr)) 

Utiliza las siguientes funciones y tipos.

Primero algunos sinónimos tipo para hacer que el código sea más legible.

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 función principal reutilizable. Le pasamos la lista de funciones para generar las funciones de la instancia.

-- 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) 

Una función auxiliar de las anteriores.

-- 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 [email protected](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 

Y algunos códigos de ayuda tomados de Syb III/replib 0.2.

typeInfo :: DecQ -> Q (Name, [Name], [(Name, Int)], [(Name, [(Maybe Name, Type)])]) 
typeInfo m = 
    do d <- m 
     case d of 
      [email protected](DataD _ _ _ _ _) -> 
      return $ (simpleName $ name d, paramsA d, consA d, termsA d) 
      [email protected](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 
Cuestiones relacionadas