8

有没有办法编写以下内容:

 {-# LANGUAGE DeriveDataTypeable #-}
 {-# LANGUAGE DeriveAnyClass     #-}

 data X = A | B | C
     deriving (Eq, Ord, Show, Read, Data, SymWord, HasKind, SMTValue)

这样该deriving子句可以以某种方式缩短,如下所示:

 data X = A | B | C deriving MyOwnClass

如果可能的话,我想避免使用 TH,我很高兴创建一个新类,根据需要将所有这些派生类作为其超类(如上MyOwnClass所示),但这并不适用于deriving机制。通过约束种类扩展,我发现你可以这样写:

type MyOwnClass a = (Eq a, Ord a, Show a, Read a, Data a, SymWord a, HasKind a, SMTValue a)

不幸的是,我不能把它放在deriving条款中。有什么魔法可以让这一切发生吗?

编辑从评论看来,TH 可能是这里唯一可行的选择。(CPP 宏真的不行!)如果是这样,TH 解决方案的草图将会很高兴看到。

4

1 回答 1

8

坏而简单的方法,也有好的但困难的方法。正如Silvio Mayolo所说,你可以用它TemplateHaskell来编写这样的函数。这种方式是困难且相当复杂的方式。更简单的方法是像这样使用 C 预处理器:

{-# LANGUAGE CPP #-}

#define MY_OWN_CLASS (Eq, Ord, Show, Read, Data, SymWord, HasKind, SMTValue)

data X = A | B | C
     deriving MY_OWN_CLASS

更新(17.07.2016): TH 解决方案的想法和草图

在介绍解决方案草图之前,我将说明为什么使用 TH 更难做到这一点。deriving-clause 不是一些独立的子句,它是声明的一部分,所以不幸的data是你不能只编码里面的一部分。deriving编写任何 TH 代码的一般方法是使用runQ括号中的命令来查看最后应该写什么。像这样:

ghci> :set -XTemplateHaskell
ghci> :set -XQuasiQuotes 
ghci> import Language.Haskell.TH
ghci> runQ [d|data A = B deriving (Eq, Show)|]
[ DataD
    []
    A_0
    []
    Nothing
    [ NormalC B_1 [] ]
    [ ConT GHC.Classes.Eq , ConT GHC.Show.Show ]
]

现在你看到类型类deriving被指定为DataD——数据声明——构造函数的最后一个参数。您的问题的解决方法是使用-XStadandaloneDerivingextension。它就像deriving但非常强大,但也非常冗长。同样,要查看您想要生成的内容,只需使用runQ

ghci> data D = T
ghci> :set -XStandaloneDeriving 
ghci> runQ [d| deriving instance Show D |]
[ StandaloneDerivD [] (AppT (ConT GHC.Show.Show) (ConT Ghci5.D)) ]

您可以StandaloneDerivD直接使用和其他构造函数,也可以只使用[d|...|]-brackets,尽管它们具有更多魔力,但它们会为您提供Dec(声明)列表。如果要生成多个声明,则应编写如下函数:

{-# LANGUAGE TemplateHaskell    #-}
{-# LANGUAGE QuasiQuotes        #-}
{-# LANGUAGE StandaloneDeriving #-}

module Deriving where

import Language.Haskell.TH

boilerplateAnnigilator :: Name -> Q [Dec]
boilerplateAnnigilator typeName = do
    let typeCon = conT typeName
    [d|deriving instance Show $(typeCon)
       deriving instance Eq   $(typeCon)
       deriving instance Ord  $(typeCon)
      |]

简要教程可以在这里找到

然后你可以在另一个文件中使用它(这是称为阶段性限制的 TH 限制:你应该在一个文件中定义,但不能在同一个文件中使用它),如下所示:

{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TemplateHaskell    #-}

import Deriving

data X = A | B | C

boilerplateAnnigilator ''X

你应该把你想要的其他类型类放在boilerplateAnnigilator函数中。但这种方法仅适用于非参数化类。如果您有独立派生data MyData a = ...,则应如下所示:

deriving instance Eq a => MyData a

如果您希望您的 TH 宏也适用于参数化类,那么您基本上应该通过推断类型是否具有类型变量并根据该类型生成实例来实现 GHC 编译器的整个逻辑。但这要困难得多。我认为最好的解决方案是在 GHC 编译器中制作票证,并让作者实现这种称为派生别名的功能:)

于 2017-07-15T14:52:33.727 回答