【问题标题】:How does deriving work in Haskell?在 Haskell 中推导是如何工作的?
【发布时间】:2011-04-21 08:21:48
【问题描述】:
Haskell 中的

代数数据类型 (ADT) 可以自动成为某些typeclasse 的实例(例如ShowEq)通过从它们派生

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

我的问题是,deriving 是如何工作的,即 Haskell 如何知道如何为派生 ADT 实现派生类型类的功能?

另外,为什么deriving 仅限于某些类型类?为什么我不能编写自己的可以派生的类型类?

【问题讨论】:

    标签: haskell typeclass derived-types derived-instances deriving


    【解决方案1】:

    简短的回答是,魔法:-)。这就是说自动派生被纳入 Haskell 规范,每个编译器都可以选择以自己的方式实现它。然而,关于如何使其可扩展,还有很多工作要做。

    Derive 是 Haskell 的一个工具,可以让你编写自己的派生机制。

    GHC 曾经提供名为Generic Classes 的可派生类型类扩展,但它很少使用,因为它有些弱。 现在已将其删除,并且正在进行整合本文所述的新通用派生机制的工作:http://www.dreixel.net/research/pdf/gdmh.pdf

    有关更多信息,请参阅:

    【讨论】:

    【解决方案2】:

    来自 Haskell 98 报告:

    Prelude 中唯一允许派生实例的类是 Eq、Ord、Enum、Bounded、Show 和 Read...

    这里是如何派生这些类型类的描述:http://www.haskell.org/onlinereport/derived.html#derived-appendix

    【讨论】:

      【解决方案3】:

      可以使用Template Haskell 以类似于派生子句的方式生成实例声明。

      下面的例子是从Haskell Wiki无耻盗用的:

      在本例中,我们使用以下 Haskell 代码

      $(gen_render ''Body)
      

      产生以下实例:

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

      上面的函数gen_render定义如下。 (请注意,此代码必须位于与上述用法不同的模块中)。

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

      其中使用以下函数和类型。

      首先一些类型同义词使代码更具可读性。

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

      主要的可重用功能。我们将函数列表传递给它以生成实例的函数。

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

      上述的辅助函数。

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

      还有一些从 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
      

      【讨论】:

        猜你喜欢
        • 2016-12-06
        • 2012-04-23
        • 2012-09-08
        • 2012-04-01
        • 1970-01-01
        • 2016-09-16
        • 2015-04-02
        • 1970-01-01
        相关资源
        最近更新 更多