使用Template Haskell我想生成记录,例如:
data MyRecordA = MyRecordA
{fooA :: String, barA :: Bool}
MyRecordA 中的大写 A , fooA , barA 以及类型 Bool 应该是可变的,并由TH函数的调用者指定。
我尝试了几种变体:
{-# LANGUAGE TemplateHaskell #-}
module THRecord where
import Language.Haskell.TH
mkRecord :: Name -> Name -> Q [Dec]
mkRecord name cls = [d|
data $typeName :: $constName
{$fieldFoo, $fieldBar}
|]
where
typeName = conT $ "MyRecord" <> name
constrName = RecC $ "MyRecord" <> name
fieldFoo = sigP name ($clsString)
fieldBar = sigP name cls
clsString = conT "String"
不幸的是,我得到了像
这样的解析错误src / THRecord.hs:8:9:错误:输入'$ fieldFoo'上的解析错误
答案 0 :(得分:2)
这里有几个问题;让我们逐一看看它们。你有拼接:
[d|
data $typeName :: $constName
{$fieldFoo, $fieldBar}
|]
根本无效;您只能拼接整个表达式,类型或声明,而不能拼接其中的一部分。您也可能意味着data $typeName = $constName
,但当然同样的限制也适用于此,因此它仍然无法工作。
定义
fieldFoo = sigP name ($clsString)
不起作用,因为如果没有介入报价,您可能没有局部变量的拼接。这被称为“阶段限制”。
fieldFoo = sigP name ($clsString)
fieldBar = sigP name cls
sigP
是错误的,因为它构造了一个模式;你不需要建立任何模式(不确定你的意思)。
typeName = conT $ "MyRecord" <> name
constrName = RecC $ "MyRecord" <> name
clsString = conT "String"
所有这些都试图将Name
视为String
。如果不清楚为什么这没有意义,也许你应该熟悉Haskell的基础知识。
现在的解决方案:
import Data.Monoid
import Language.Haskell.TH
import Language.Haskell.TH.Syntax
defBang = Bang NoSourceUnpackedness NoSourceStrictness
stringType = ConT ''String
mkRecord :: Name -> Name -> Q [Dec]
mkRecord name cls = (pure.pure)$
DataD [] typeName [] Nothing [constr] []
where
typeName = mkName $ "MyRecord" <> nameBase name
constr = RecC typeName [(mkName $ "foo" <> nameBase name, defBang, stringType)
,(mkName $ "bar" <> nameBase name, defBang, ConT cls)]
请注意,您甚至不能在这里使用Q
monad;不要生成名称,也不要重新确定有关名称的信息。因此,您实际上可以编写函数Name -> Name -> Dec
,然后将pure.pure
应用于结果会生成一个可以拼接的类型。
上述内容适用于GHC 8.0.1;模板Haskell的AST在主要版本之间存在很大差异,因此它可能无法像其他版本那样完全编译。
然后,例如
$(mkRecord (mkName "XYZ") ''Bool)
$(mkRecord (mkName "A") ''Int)
生成
data MyRecordXYZ = MyRecordXYZ {fooXYZ :: String, barXYZ :: Bool}
data MyRecordA = MyRecordA {fooA :: String, barA :: Int}
最后,这是一个不需要TH的解决方案。您希望生成的类型系列可以用第一类方式表示:
import GHC.TypeLits
data MyRecord (nm :: Symbol) t = MyRecord { foo :: String, bar :: t }
type MyRecordA = MyRecord "A" Bool
type MyRecordXYZ = MyRecord "XYZ" Int