【问题标题】:Template Haskell: Generate Records模板 Haskell:生成记录
【发布时间】:2017-12-27 01:48:54
【问题描述】:

使用 Template Haskell 我想生成记录,例如:

data MyRecordA = MyRecordA
  {fooA :: String, barA :: Bool} 

MyRecordAfooAbarA中的大写A和类型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”时出现解析错误

【问题讨论】:

    标签: haskell template-haskell


    【解决方案1】:

    这里有几个问题;让我们一一看看。您拥有的接头:

    [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 -&gt; Name -&gt; 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
    

    【讨论】:

    • 我特别喜欢没有 TH 的解决方案!
    • 不幸的是,不错的无 TH 解决方案有一个小怪癖:它不会为数据构造函数生成不同的名称。它总是生成 MyRecord 而不是 MyRecordAMyRecordXYZ
    • @Jogger 确实如此,但这并不会真正让您付出任何代价。您仍然可以将构造函数的类型限制为任何名称,例如MyRecord :: String -&gt; t -&gt; MyRecord "X" t。使用-XTypeApplications,您甚至可以编写`MyRecord @"X"`
    猜你喜欢
    • 1970-01-01
    • 2016-10-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-07-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多