我想用DSum
来表达某些东西。要使用DSum
,您需要有一个'标记'采用一种类型参数的类型,例如
data Tag a where
AFirst :: Tag Int
ASecond :: Tag String
但是,我想在库中内部使用它。我希望我向用户公开的接口采用任何旧的数据类型,例如
data SomeUserType1 = Foo Int | Bar String
从上面给出的Tag a
类型,它显然非常机械化。那么,是否可以通过某种通用编程技术在代码中执行此操作?
这是另一个关于我想要制作的映射类型的例子。
data SomeUserType2 = Foo Int | Bar Char | Baz Bool String
应该成为
data Tag2 a where
AFirst :: Tag2 Int
ASecond :: Tag2 Char
AThird :: Tag2 (Bool, String)
这是Template Haskell的工作吗?别的什么?我甚至不知道这里有什么选择。
答案 0 :(得分:4)
模板Haskell是你想要的,因为你试图生成声明。这是有效的。将以下内容放在名为Tag.hs
的文件中:
{-# LANGUAGE TemplateHaskell #-}
module Tag where
import Language.Haskell.TH
makeTag :: Name -> DecsQ
makeTag name = do
-- Reify the data declaration to get the constructors.
-- Note we are forcing there to be no type variables...
(TyConI (DataD _ _ [] _ cons _)) <- reify name
pure [ DataD [] tagTyName [PlainTV (mkName "a")] Nothing (fmap tagCon cons) [] ]
where
-- Generate the name for the new tag GADT type constructor.
tagTyName :: Name
tagTyName = mkName ("Tag" ++ nameBase name)
-- Given a constructor, construct the corresponding constructor for the GADT.
tagCon :: Con -> Con
tagCon (NormalC conName args) =
let tys = fmap snd args
tagType = foldl AppT (TupleT (length tys)) tys
in GadtC [mkName ("Tag" ++ nameBase conName)] []
(AppT (ConT tagTyName) tagType)
然后你可以在另一个文件中测试它:
{-# LANGUAGE TemplateHaskell, GADTs #-}
import Tag
data SomeUserType1 = Foo Int | Bar String
data SomeUserType2 = Fooo Int | Baar Char | Baaz Bool String
makeTag ''SomeUserType1
makeTag ''SomeUserType2
如果您检查GHCi中的第二个文件(或通过将-ddump-splices
传递给ghci
或ghc
来查看生成的代码),您将看到生成以下内容:
data TagSomeUserType1 a where
TagFoo :: TagSomeUserType1 Int
TagBar :: TagSomeUserType1 String
data TagSomeUserType3 a where
TagFooo :: TagSomeUserType2 Int
TagBaar :: TagSomeUserType2 Char
TagBaaz :: TagSomeUserType2 (Bool, String)
我必须使用mkName
和而不是 newName
,因为如果您希望使用这些生成的GADT,那么您需要它们具有可预测的名称你可以写。从示例中可以清楚地看出,我的约定是将Tag
添加到类型和数据构造函数中。