在 Haskell 中推导是如何工作的?
Posted
技术标签:
【中文标题】在 Haskell 中推导是如何工作的?【英文标题】:How does deriving work in Haskell? 【发布时间】:2011-04-21 08:21:48 【问题描述】: Haskell 中的代数数据类型 (ADT) 可以自动成为某些typeclasse 的实例(例如Show
、Eq
)通过从它们派生。
data Maybe a = Nothing | Just a
deriving (Eq, Ord)
我的问题是,deriving
是如何工作的,即 Haskell 如何知道如何为派生 ADT 实现派生类型类的功能?
另外,为什么deriving
仅限于某些类型类?为什么我不能编写自己的可以派生的类型类?
【问题讨论】:
【参考方案1】:简短的回答是,魔法:-)。这就是说自动派生被纳入 Haskell 规范,每个编译器都可以选择以自己的方式实现它。然而,关于如何使其可扩展,还有很多工作要做。
Derive 是 Haskell 的一个工具,可以让你编写自己的派生机制。
GHC 曾经提供名为Generic Classes 的可派生类型类扩展,但它很少使用,因为它有些弱。 现在已将其删除,并且正在进行整合本文所述的新通用派生机制的工作:http://www.dreixel.net/research/pdf/gdmh.pdf
有关更多信息,请参阅:
GHC 维基:http://hackage.haskell.org/trac/ghc/wiki/Commentary/Compiler/GenericDeriving Haskell 维基:http://www.haskell.org/haskellwiki/Generics 黑客:http://hackage.haskell.org/package/generic-deriving【讨论】:
另见StandaloneDeriving
in the ghc manual 和 haskellwiki
仅供参考,haskell.org/onlinereport/haskell2010/haskellch11.html 明确指定了魔法。【参考方案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
【讨论】:
以上是关于在 Haskell 中推导是如何工作的?的主要内容,如果未能解决你的问题,请参考以下文章