我如何以编程方式从另一个数据类型生成此数据类型?

时间:2016-11-14 05:15:37

标签: haskell generic-programming template-haskell gadt

我想用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的工作吗?别的什么?我甚至不知道这里有什么选择。

1 个答案:

答案 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传递给ghcighc来查看生成的代码),您将看到生成以下内容:

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添加到类型和数据构造函数中。