题
我发现自己写了很多代码,如
putStr "foo (bar 1) (bar 2) =" print $ foo (bar 1) (bar 2).
问题是,打印的消息可以与实际执行的代码不同步。显而易见的解决方案是自动生成此代码。
一种方法是将所有文本放在文件中,并编写一个读取文件的小程序,并从中生成Haskell源代码。但另一种替代方案是使用模板haskell。
是否有人知道如何编写一个函数,该函数采用一个生成的函数,并从中生成上面的代码?我猜这应该很容易,但是没有充分记录。
解决方案
可以使用 haskell-src-meta
包来解析Haskell代码。这是一个快速示例,你如何将其与模板Haskell结合起来。
{-# LANGUAGE TemplateHaskell #-}
import Language.Haskell.TH
import Language.Haskell.TH.Quote
import Language.Haskell.Meta
runShow = QuasiQuoter
{ quoteExp = runShowQQ
, quotePat = undefined
, quoteType = undefined
, quoteDec = undefined
}
runShowQQ :: String -> Q Exp
runShowQQ s = do
let s' = s ++ " = "
Right exp = parseExp s
printExp = appE [|print|] (return exp)
infixApp [|putStr s'|] [|(>>)|] printExp
.
,你会像这样使用它
{-# LANGUAGE QuasiQuotes #-}
[runShow|foo (bar 1) (bar 2)|]
. 其他提示
模板Haskell不提供解析任意字符串的直接方法,因此最简单的解决方案可能是使用C预处理器。但是,GHC中的内置一个不支持纵横化,因此我们需要传递额外的选项来使用“真实”。
{-# LANGUAGE CPP #-}
{-# OPTIONS_GHC -pgmP cpp #-}
#define PRINT_EXP(x) (putStr #x >> putStr " = " >> print (x))
.
然后您可以使用它:
PRINT_EXP(foo (bar 1) (bar 2))
. 使用ghc api 在这里 <。
哎哟。我的想法这很容易,但据我所知,它实际上是不可能。
我期望有一个函数将字符串变成表达式,但显然没有存在这样的功能。甚至没有一个函数来从磁盘加载更多源代码。所以这似乎这项任务实际上是不可能的!我很惊讶。
我可以做的最接近的事情是引用我想要运行的表达式,然后在运行它之前构建漂亮打印引用的表达式的拼接。然而,这让我怜悯GHC的表达式漂亮的打印机。当我键入它时,标签不会完全出来。 (特别是,它似乎用完全合格的名称替换运营商,这只是痛苦。)
你会认为这样的功能会非常微不足道。因此,它没有实现的事实只能归因于两件事之一:
-
没有人实际上需要这个功能。 (很好,除了我,显然是。)
-
它不会像看起来一样微不足道。 (例如,也许弄清楚解析表达式的上下文某种情况以某种方式而言?)
您也可以使用转储包编写的包装以处理此确切用例:
{-# language QuasiQuotes #-}
import Debug.Dump
main = putStrLn [d| foo (bar 1) (bar 2) |]
foo = (+)
bar = (+1)
.
哪个打印:(foo (bar 1) (bar 2)) = 5
它还处理以逗号分隔的多个表达式:
putStrLn [d| foo (bar 1) (bar 2), map bar [1, 2] |]
.
哪个打印:(foo (bar 1) (bar 2)) = 5 (map bar [1, 2]) = [2,3]
奖金: 如果您安装了nix-shell(部分 nix包管理器)您甚至可以用它快速尝试“单线”:
$ nix-shell -p "nix-shell -p "haskellPackages.ghcWithPackages (p: [p.dump])" --run "echo '{-# language QuasiQuotes #-}; import Debug.Dump; foo = (+); bar = (+1); main = putStrLn [d| foo (bar 1) (bar 2), map bar [1, 2] |]' | runhaskell"