有没有办法缩短这个派生子句?

Is there a way to shorten this deriving clause?

有没有办法这样写:

 {-# 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,并且我很乐意创建一个新的 class,其中包含所有派生的 classes 作为它的超级 class 根据需要(如上面的 MyOwnClass 所示),但这并不真正适用于 deriving 机制。使用约束种类扩展,我发现你可以这样写:

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

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

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

不好但容易的方法和好但难的方法。正如 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 的类型 class 被指定为 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)
      |]

简要教程can be found here

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

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

import Deriving

data X = A | B | C

boilerplateAnnigilator ''X

你应该把你想要的其他类型 class 放在 boilerplateAnnigilator 函数中。但是这种方法只适用于非参数化class。如果你有 data MyData a = ... 那么 独立派生 应该是这样的:

deriving instance Eq a => MyData a

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

的功能