有坏而简单的方法,也有好的但困难的方法。正如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
——数据声明——构造函数的最后一个参数。您的问题的解决方法是使用-XStadandaloneDeriving
extension。它就像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 编译器中制作票证,并让作者实现这种称为派生别名的功能:)