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

Wiz*_*zek 9 haskell template-haskell

例如:

let x = 1 in putStrLn [dump|x, x+1|]
Run Code Online (Sandbox Code Playgroud)

会印出类似的东西

x=1, (x+1)=2
Run Code Online (Sandbox Code Playgroud)

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

kqr*_*kqr 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)|]
Run Code Online (Sandbox Code Playgroud)

你有能力做类似的事情

?> 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"]
Run Code Online (Sandbox Code Playgroud)

这几乎是你想要的.如您所见,这是一个问题,该pprint功能包括模块前缀等,这使得结果......不太理想.我还不知道有什么修复方法,但除此之外,我认为它非常实用.

它有点语法沉重,但这是因为它[|在Haskell中使用常规引用语法.如果有人想写自己的quasiquoter,正如你的建议,我很确定你还需要重新实现解析Haskell,这会有点麻烦.


Wiz*_*zek 5

TL; DR这个包包含一个完整的解决方案.

  • 通过安装它 cabal install dump

    和/或

  • 阅读源代码

用法示例:

{-# LANGUAGE QuasiQuotes #-}

import Debug.Dump

main = print [d|a, a+1, map (+a) [1..3]|]
  where a = 2
Run Code Online (Sandbox Code Playgroud)

打印:

(a) = 2   (a+1) = 3       (map (+a) [1..3]) = [3,4,5]
Run Code Online (Sandbox Code Playgroud)

通过turnint这个String

"a, a+1, map (+a) [1..3]"
Run Code Online (Sandbox Code Playgroud)

进入这个表达

( "(a) = " ++ show (a)            ++ "\t  " ++
  "(a+1) = " ++ show (a + 1)      ++ "\t  " ++
  "(map (+a) [1..3]) = " ++ show (map (+ a) [1 .. 3])
)
Run Code Online (Sandbox Code Playgroud)

背景

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

  1. Exp -> String这里的瓶颈是漂亮的打印haskell源代码Exp和使用时繁琐的语法.
  2. String -> Exp这里的瓶颈是解析haskell Exp.

Exp -> String

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

["GHC.Classes.not x_1627412787 = False","x_1627412787 = True","x_1627412787 GHC.Classes.== GHC.Types.True = True"]
Run Code Online (Sandbox Code Playgroud)

进入这个

["not x = False","x = True","x == True = True"]
Run Code Online (Sandbox Code Playgroud)

但是在尝试了一天后,我的parsec-debugging-skills已经证明不适合约会,所以我选择了一个简单的正则表达式:

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

在大多数情况下,输出大大提高.但是,我怀疑这可能会错误地删除它不应该删除的东西.

例如:

$(dump [|(elem 'a' "a.b.c", True)|])
Run Code Online (Sandbox Code Playgroud)

可能会回归:

["elem 'a' \"c\" = True","True = True"]
Run Code Online (Sandbox Code Playgroud)

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

以下版本适用于正则表达式简化: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
Run Code Online (Sandbox Code Playgroud)

据我所知,在编译时,它变成了

string = "The quick " ++ "brown" ++ " " ++ $f ++ "jumps " ++ o ++ " the" ++ show num ++ " ..."
  where f = "fox"; o = "over"; num = 3
Run Code Online (Sandbox Code Playgroud)

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

他们的源代码中有点挖掘了QuasiQuoter类型.

data QuasiQuoter = QuasiQuoter {quoteExp :: String -> Q Exp}
Run Code Online (Sandbox Code Playgroud)

宾果,这就是我想要的!给我源代码作为字符串!理想情况下,我也不介意返回字符串,但也许这会起作用.在这一点上,我仍然知之甚少Q Exp.

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

转过来:

[d|a+1|]
Run Code Online (Sandbox Code Playgroud)

进入这个:

"a+1" ++ " = " ++ show (a+1)
Run Code Online (Sandbox Code Playgroud)

听起来很简单吧?

好吧,事实证明,即使GHC最明显能够解析haskell源代码,它也不会公开该函数.或者我们不知道.

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

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

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

Language.Haskell.Meta.Parse.parseExp :: String -> Either String Exp
Run Code Online (Sandbox Code Playgroud)

Language.Haskell.Meta.Parse

从这一点来看,除了分裂逗号之外,它是相当简单的.

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

[d|(1, 2), 3|]
Run Code Online (Sandbox Code Playgroud)

哪个不幸失败了.为了解决这个问题,我开始编写一个parsec解析器(再次),结果比预期更困难(再次).在这一点上,我愿意接受建议.也许您知道一个处理不同边缘情况的简单解析器?如果是的话,请在评论中告诉我!我计划在有或没有parsec的情况下解决这个问题.

但对于大多数用例:它的工作原理.

2015-06-20更新

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

您可以

  • 通过安装它 cabal install dump

    和/或

  • 阅读源代码

结论

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

也许最重要的是,我现在能够使用这个工具进行调试和开发,这种工具的缺失一直困扰着我.和平终于到来了.

谢谢你@kqr,你对我原来的问题的参与并试图解决它给了我足够的火花和动力继续写一个完整的解决方案.