Haskell 中的推导是如何进行的?

发布于 2024-09-26 03:53:42 字数 351 浏览 11 评论 0原文

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

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

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

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

Algebraic Data Types (ADTs) in Haskell can automatically become instances of some typeclasses (like Show, Eq) by deriving from them.

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

My question is, how does this deriving work, i.e. how does Haskell know how to implement the functions of the derived typeclass for the deriving ADT?

Also, why is deriving restricted to certain typeclasses only? Why can't I write my own typeclass which can be derived?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

溺渁∝ 2024-10-03 03:53:42

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

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

GHC 曾经提供一个名为 Generic 的可派生类型类扩展类,但很少使用,因为它有点弱。
现在已经删除了,并且正在努力集成新的通用派生机制,如本文所述:http://www.dreixel.net/research/pdf/gdmh.pdf

有关详细信息,请参阅:

The short answer is, magic :-). This is to say that automatic deriving is baked into the Haskell spec, and every compiler can choose to implement it in its own way. There's lots of work on how to make it extensible however.

Derive is a tool for Haskell to let you write your own deriving mechanisms.

GHC used to provide a derivable type class extension called Generic Classes, but it was rarely used, as it was somewhat weak.
That has now been taken out, and work is ongoing to integrate a new generic deriving mechanism as described in this paper: http://www.dreixel.net/research/pdf/gdmh.pdf

For more on this, see:

御守 2024-10-03 03:53:42

来自 Haskell 98 报告:

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

以下是如何派生这些类型类的说明: haskell.org/onlinereport/衍生.html#衍生-appendix" rel="noreferrer">http://www.haskell.org/onlinereport/衍生.html#衍生-appendix

From the Haskell 98 report:

The only classes in the Prelude for which derived instances are allowed are Eq, Ord, Enum, Bounded, Show, and Read...

Here's the description of how to derive these type classes: http://www.haskell.org/onlinereport/derived.html#derived-appendix

云朵有点甜 2024-10-03 03:53:42

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

以下示例无耻地从 Haskell Wiki 窃取:

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

$(gen_render ''主体)

生成以下实例:

实例 TH_Render Body 其中
  渲染(NormalB exp)=构建'normalB exp
  render (GuardedB 守卫) = build 'guardedB 守卫

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

-- 为 typeName 类型生成 TH_Render 类的实例
gen_render :: 名称 ->问 [12 月]
gen_render 类型名称 =
  do (TyConI d) <- reifytypName -- 获取类型的所有信息
     (type_name,_,_,constructors) <- typeInfo (return d) -- 提取名称和构造函数                  
     i_dec <- gen_instance (mkName "TH_Render") (conT type_name) 构造函数
                      -- 方法“render”的生成函数
                      [(mkName“渲染”,gen_render)]
     return [i_dec] -- 返回实例声明
             -- 为特定函数生成函数体的函数
             -- 和构造函数
       其中 gen_render (conName, 组件) vars 
                 -- 函数名称基于构造函数名称  
               = let funcName = makeName $ unCapalize $ nameBase conName 
                 -- 选择正确的构建器函数
                     headFunc = 案例变量
                                     []-> “功能输出”
                                     否则-> “建造” 
                      -- 构建 'funcName parm1 parm2 parm3 ...
                   in appsE $ (varE $ mkName headFunc):funcName:vars -- 把它们放在一起
             -- 相当于 'funcStr,其中 funcStr 包含要返回的名称
             makeName funcStr = (appE (varE (mkName "mkName")) (litE $StringL funcStr))

它使用以下函数和类型。

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

type Constructor = (Name, [(Maybe Name, Type)]) -- 构造函数列表
type Cons_vars = [ExpQ] -- 构造函数中绑定的变量列表
类型 Function_body = ExpQ 
类型 Gen_func = 构造函数 -> Cons_vars ->;函数体
type Func_name = Name -- 我们将创建的实例函数的名称
-- 对于实例中的每个函数,我们提供一个生成器函数
-- 生成函数体(为每个构造函数生成函数体)
类型 Funcs = [(Func_name, Gen_func)]

主要的可重用函数。我们向它传递函数列表来生成实例的函数。

--为类型 for_type 构造类 class_name 的实例
-- funcs 是实例方法名称及其对应的列表
-- 构建方法体的函数
gen_instance :: 名称 ->类型Q-> [构造函数]->功能->十二月
gen_instance 类名 for_type 构造函数 funcs = 
  实例D(cxt[])
    (appT (conT 类名) for_type)
    (映射 func_def 函数) 
      其中 func_def (func_name, gen_func) 
                = funD func_name -- 方法名
                  -- 为每个构造函数生成函数体
                  (映射(gen_clause gen_func)构造函数)

上述的辅助函数。

-- 为给定方法生成模式匹配和函数体
-- 给定的构造函数。 func_body 是一个生成
-- 函数体
gen_clause :: (构造函数 -> [ExpQ] -> ExpQ) ->构造函数->条款Q
gen_clause func_body data_con@(con_name, 组件) = 
      -- 为构造函数的每个组件创建一个参数
   do vars <-mapM var 组件
      -- 模式与构造函数匹配的函数(未命名) 
      -- 将每个组件映射到一个值。
      (子句 [(conP con_name (map varP vars))]
            (normalB (func_body data_con (map varE vars))) [])
       -- 为每个组件创建唯一的名称。 
       其中 var (_, 典型值) 
                 = 新名称 
                   $ 案例类型 
                     (续名称)-> toL $ name 基本名称
                     否则-> “帕尔姆”
               其中 toL (x:y) = (toLower x):y

unCapalize :: [字符] -> [查尔]
取消大写 (x:y) = (降低 x):y

还有一些借用自 Syb III/replib 0.2 的帮助程序代码。

typeInfo :: DecQ ->; Q(名称,[名称],[(名称,整数)],[(名称,[(可能名称,类型)])])
类型信息 m =
     d <-m
        情况d
           d@(DataD _ _ _ _ _) ->
            返回 $(simpleName $ 名称 d, paramsA d, consA d, termsA d)
           d@(NewtypeD _ _ _ _ _) ->
            返回 $(simpleName $ 名称 d, paramsA d, consA d, termsA d)
           _->错误(“派生:不是数据类型声明:” ++ show d)

     在哪里
        consA (DataD _ _ _ cs _) = 映射 conA cs
        consA (NewtypeD _ _ _ c _) = [ conA c ]

        {- 这部分不再适用于 7.6.3
        paramsA (DataD _ _ ps _ _) = ps
        paramsA (NewtypeD _ _ ps _ _) = ps
        -}

        -- 在更新的 GHC 上使用这个而不是上面的
        paramsA (DataD _ _ ps _ _) = 地图名称FromTyVar ps
        paramsA (NewtypeD _ _ ps _ _) = 地图名称FromTyVar ps

        nameFromTyVar (PlainTV a) = a
        nameFromTyVar (KindedTV a _) = a


        termsA (DataD _ _ _ cs _) = 映射 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, [(无, snd t1), (无, snd t2)])

        conA (NormalC c xs) = (simpleName c, 长度 xs)
        conA (RecC c xs) = (simpleName c, 长度 xs)
        conA (InfixC _ c _) = (simpleName c, 2)

        名称 (DataD _ n _ _ _) = n
        名称 (NewtypeD _ n _ _ _) = n
        名称 d = 错误 $ 显示 d

simpleName :: 名称 ->姓名
简单名称 nm =
   让 s = nameBase nm
   如果 dropWhile (/=':') s 的
        []-> mkName s
        _:[] -> mkName s
        _:t->名称 t

It is possible to use Template Haskell to generate instance declarations in a similar way to deriving-clauses.

The following example is shamelessly stolen from the Haskell Wiki:

In this example we use the following Haskell code

$(gen_render ''Body)

to produce the following instance:

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

The function gen_render above is defined as follows. (Note that this code must be in separate module from the above usage).

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

Which uses the following functions and types.

First some type synonyms to make the code more readable.

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

The main reusable function. We pass it the list of functions to generate the functions of the 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)

A helper function of the above.

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

And some borrowed helper code taken from 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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文