模板 Haskell:生成记录
Template Haskell: Generate Records
使用模板 Haskell 我想生成记录,例如:
data MyRecordA = MyRecordA
{fooA :: String, barA :: Bool}
A中的大写MyRecordA,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: error: parse error on input ‘$fieldFoo’
这里有几个问题;让我们一一看看。您拥有的接头:
[d|
data $typeName :: $constName
{$fieldFoo, $fieldBar}
|]
根本无效;您只能拼接整个表达式、类型或声明,而不是其中的一部分。你也可能意味着 data $typeName = $constName
但当然同样的限制适用于它,所以它仍然行不通。
定义
fieldFoo = sigP name ($clsString)
不起作用,因为如果没有中间引号,您可能无法拼接局部变量。这被称为 'stage restriction'.
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 的解决方案。您希望生成的类型族可以用第一种 class 方式表示:
import GHC.TypeLits
data MyRecord (nm :: Symbol) t = MyRecord { foo :: String, bar :: t }
type MyRecordA = MyRecord "A" Bool
type MyRecordXYZ = MyRecord "XYZ" Int
使用模板 Haskell 我想生成记录,例如:
data MyRecordA = MyRecordA
{fooA :: String, barA :: Bool}
A中的大写MyRecordA,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: error: parse error on input ‘$fieldFoo’
这里有几个问题;让我们一一看看。您拥有的接头:
[d|
data $typeName :: $constName
{$fieldFoo, $fieldBar}
|]
根本无效;您只能拼接整个表达式、类型或声明,而不是其中的一部分。你也可能意味着 data $typeName = $constName
但当然同样的限制适用于它,所以它仍然行不通。
定义
fieldFoo = sigP name ($clsString)
不起作用,因为如果没有中间引号,您可能无法拼接局部变量。这被称为 'stage restriction'.
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 的解决方案。您希望生成的类型族可以用第一种 class 方式表示:
import GHC.TypeLits
data MyRecord (nm :: Symbol) t = MyRecord { foo :: String, bar :: t }
type MyRecordA = MyRecord "A" Bool
type MyRecordXYZ = MyRecord "XYZ" Int