这里有几个问题;让我们一一看看。您拥有的接头:
[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; Template 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