是否有(模板)Haskell库允许我使用各自的名称打印/转储一些本地绑定?

时间:2015-07-10 20:16:25

标签: haskell template-haskell

例如:

let x = 1 in putStrLn [dump|x, x+1|]

会打印类似

的内容
x=1, (x+1)=2

即使目前还没有这样的东西,是否有可能写出类似的内容?

2 个答案:

答案 0 :(得分:5)

我现在几乎已经解决了这个问题。不完全是你想象的,但相当接近。也许其他人可以使用它作为更好版本的基础。无论哪种方式,

{-# LANGUAGE TemplateHaskell, LambdaCase #-}

import Language.Haskell.TH

dump :: ExpQ -> ExpQ
dump tuple =
    listE . map dumpExpr . getElems =<< tuple
  where
    getElems = \case { TupE xs -> xs; _ -> error "not a tuple in splice!" }
    dumpExpr exp = [| $(litE (stringL (pprint exp))) ++ " = " ++ show $(return exp)|]

你可以做一些像

这样的事情
λ> let x = True
λ> print $(dump [|(not x, x, x == True)|])
["GHC.Classes.not x_1627412787 = False","x_1627412787 = True","x_1627412787 GHC.Classes.== GHC.Types.True = True"]

这几乎是你想要的。如您所见,问题是pprint函数包含模块前缀等,这使得结果......不太理想。我还不知道修复此问题,但除此之外,我认为它非常实用。

它在语法上有点沉重,但这是因为它在Haskell中使用了常规的[|引用语法。如果有人想写自己的quasiquoter,正如你的建议,我非常肯定还需要重新实现解析Haskell,这会让人感到厌烦。

答案 1 :(得分:5)

TL; DR this package包含完整的解决方案。

使用示例:

{-# LANGUAGE QuasiQuotes #-}

import Debug.Dump

main = print [d|a, a+1, map (+a) [1..3]|]
  where a = 2

打印:

(a) = 2   (a+1) = 3       (map (+a) [1..3]) = [3,4,5]

通过turnint this String

"a, a+1, map (+a) [1..3]"

进入这个表达式

( "(a) = " ++ show (a)            ++ "\t  " ++
  "(a+1) = " ++ show (a + 1)      ++ "\t  " ++
  "(map (+a) [1..3]) = " ++ show (map (+ a) [1 .. 3])
)

背景

基本上,我发现有两种方法可以解决这个问题:

  1. Exp -> String这里的瓶颈是来自Exp的漂亮的打印haskell源代码,以及使用时的繁琐语法。
  2. String -> Exp这里的瓶颈是将haskell解析为Exp
  3. Exp -> String

    我从@kqr放在一起开始,并试图编写一个解析器来解决这个问题

    ["GHC.Classes.not x_1627412787 = False","x_1627412787 = True","x_1627412787 GHC.Classes.== GHC.Types.True = True"]
    

    进入这个

    ["not x = False","x = True","x == True = True"]
    

    但是在尝试了一天之后my parsec-debugging-skills have proven insufficient to date,所以我选择了一个简单的正则表达式:

    simplify :: String -> String
    simplify s = subRegex (mkRegex "_[0-9]+|([a-zA-Z]+\\.)+") s ""
    

    对于大多数情况,输出会大大改善。 但是,我怀疑这可能会错误地删除不应该删除的东西。

    例如:

    $(dump [|(elem 'a' "a.b.c", True)|])
    

    可能会回归:

    ["elem 'a' \"c\" = True","True = True"]
    

    但这可以通过适当的解析来解决。

    以下版本适用于正则表达式辅助简化:https://github.com/Wizek/kqr-stackoverflow/blob/master/Th.hs

    以下列出了我在Exp -> String解决方案中发现的缺点/未解决的问题:

    • 据我所知,不使用Quasi Quotation需要在使用时使用繁琐的语法,例如:$(d [|(a, b)|]) - 而不是更简洁的[d|a, b|]。如果你知道一种简化方法,请告诉我们!
    • 据我所知,[||]需要包含完全有效的Haskell,这几乎需要使用元组内部进一步加剧语法情况。然而,这也有一些好处:至少我们不需要抓住我们在哪里分割表达式,因为GHC为我们做了这些。
    • 由于某种原因,元组似乎只接受布尔人。很奇怪,我怀疑这应该可以以某种方式解决。
    • 非常漂亮的打印Exp不是很直截了当。毕竟,更完整的解决方案确实需要解析器。
    • 打印AST会擦除原始格式以获得更均匀的外观。我希望在输出中逐个字母地保留表达式。

    交易破坏者是句法上的头脑。我知道我可以使用更简单的解决方案,例如[d|a, a+1|],因为我已经看到其他包中提供的API。我试图记住我在哪里看到这种语法。名字是什么......?

    String -> Exp

    Quasi Quotation就是名字,我记得!

    我记得看过带有heredocs和插值字符串的包,比如:

    string = [qq|The quick {"brown"} $f {"jumps " ++ o} the $num ...|]
      where f = "fox"; o = "over"; num = 3
    

    据我所知,在编译期间,转为

    string = "The quick " ++ "brown" ++ " " ++ $f ++ "jumps " ++ o ++ " the" ++ show num ++ " ..."
      where f = "fox"; o = "over"; num = 3
    

    我心想:如果他们能做到,我也应该能做到!

    在源代码中挖掘了一些QuasiQuoter类型。

    data QuasiQuoter = QuasiQuoter {quoteExp :: String -> Q Exp}
    
    宾果,这就是我想要的!给我源代码作为字符串!理想情况下,我也不介意返回字符串,但也许这会奏效。在这一点上,我对Q Exp仍然知之甚少。

    毕竟,理论上,我只需要在逗号上拆分字符串,映射它,复制元素,使第一部分保持字符串,第二部分成为Haskell源代码,传递给show。

    转过来:

    [d|a+1|]
    

    进入这个:

    "a+1" ++ " = " ++ show (a+1)
    

    听起来很简单,对吧?

    嗯,事实证明,尽管GHC最明显能够解析haskell源代码,但它并没有公开该功能。或not in any way we know of

    我觉得很奇怪,我们需要一个第三方软件包(幸好至少有一个名为haskell-src-meta)来解析元编程的haskell源代码。在我看来,这是一个明显的逻辑重复,以及潜在的不匹配来源 - 导致错误。

    不情愿地,我开始研究它。毕竟,如果它对于插值字符串(那些打包确实依赖于haskell-src-meta)足够好,那么它暂时对我来说也可以正常工作。

    唉,它确实包含了所需的功能:

    Language.Haskell.Meta.Parse.parseExp :: String -> Either String Exp
    

    Language.Haskell.Meta.Parse

    从这一点开始,它非常简单,除了用逗号分割。

    现在,我对所有逗号进行了非常简单的分割,但这并不能说明这种情况:

    [d|(1, 2), 3|]
    

    不幸失败了。为了解决这个问题,I begun writing a parsec parser (again) which turned out to be more difficult than anticipated (again)。在这一点上,我愿意接受建议。也许您知道一个处理不同边缘情况的简单解析器?如果是的话,请在评论中告诉我!我计划在有或没有parsec的情况下解决这个问题。

    但对于大多数用例:它有效。

      

    2015-06-20更新

         

    版本0.2.1及更高版本正确解析表达式,即使它们中包含逗号。现在支持[d|(1, 2), 3|]和类似表达的含义。

    你可以

    结论

    在上周,我学到了很多模板Haskell和QuasiQuotation,cabal沙箱,发布了一个hackage包,构建了haddock文档并发布它们,还有一些关于Haskell的东西。 这很有趣。

    也许最重要的是,我现在能够使用这个工具进行调试和开发,缺少这些工具已经困扰了我一段时间。和平终于。

    谢谢@kqr,您对原始问题的参与并尝试解决它给了我足够的动力和动力继续编写完整的解决方案。