是否有一个(模板)Haskell 库允许我 print/dump 一些本地绑定及其各自的名称?

Is there a (Template) Haskell library that would allow me to print/dump a few local bindings with their respective names?

例如:

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

会打印类似

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

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

其实我现在已经差不多解决问题了。不完全是你想象的那样,但相当接近。也许其他人可以将其用作更好版本的基础。无论哪种方式,

{-# 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 中使用了常规的 [| 引号语法。如果有人想像你建议的那样编写自己的准引号,我很确定他们还必须重新实现解析 Haskell,这会有点糟糕。

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]

通过转换这个字符串

"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.

Exp -> String

我从 放在一起开始,并尝试编写一个解析器来转换这个

["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)|])

可能 return:

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

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

这是使用正则表达式辅助简化的版本:https://github.com/Wizek/kqr-Whosebug/blob/master/Th.hs

这是我在 Exp -> String 解决方案中发现的缺点/未解决问题的列表:

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

破坏交易的是句法开销。我知道我可以找到像 [d|a, a+1|] 这样更简单的解决方案,因为我已经看到其他包中提供了 API。我试图记住我在何处看到该语法。叫什么名字...?

String -> Exp

准语录就是这个名字,我记住了!

我记得看到过带有 heredoc 和内插字符串的包,例如:

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}

宾果,这就是我想要的!给我源代码作为字符串!理想情况下,我也不介意 returning 字符串,但也许这会起作用。在这一点上,我对Q Exp.

仍然知之甚少

毕竟,理论上,我只需要用逗号拆分字符串,映射到它上面,复制元素,以便第一部分保持字符串,第二部分成为 Haskell 源代码,即已通过展示。

正在转动这个:

[d|a+1|]

进入这个:

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

听起来很简单,对吧?

好吧,事实证明,即使 GHC 最明显能够解析 haskell 源代码,它也不会公开该功能。或者 .

我觉得很奇怪,我们需要一个第三方包(谢天谢地,至少有一个叫做 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 来解决这个问题。

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

Update at 2015-06-20

Version 0.2.1 and later correctly parses expressions even if they contain commas inside them. Meaning [d|(1, 2), 3|] and similar expressions are now supported.

你可以

结论

在上周,我学到了很多模板 Haskell 和 QuasiQuotation、cabal 沙箱、将包发布到 hackage、构建黑线鳕文档并发布它们,以及一些关于 Haskell 也。 很有趣。

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

谢谢你 ,你对我最初问题的参与和解决它的尝试给了我足够的火花和动力来继续写一个完整的解决方案。