如何用printfs"调试"Haskell?

Vin*_*inz 61 debugging trace haskell printf-debugging

来自Ocaml社区,我正在尝试学习一些Haskell.过渡进展顺利,但我对调试有点困惑.我曾经在我的ocaml代码中放置(很多)"printf",检查一些中间值,或者作为标志来查看计算完全失败的位置.

由于printf是一个IO动作,我是否必须解除IO monad中的所有haskell代码才能进行这种调试?或者有更好的方法来做到这一点(如果可以避免,我真的不想手工做)

我也找到了跟踪功能:http: //www.haskell.org/haskellwiki/Debugging#Printf_and_friends 这看起来正是我想要的,但我不明白它的类型:任何地方都没有IO!有人能解释一下跟踪功能的行为吗?

Dan*_*kov 54

trace是最容易使用的调试方法.这并不IO完全是因为你指出的原因:不需要在IOmonad中提升你的代码.它是这样实现的

trace :: String -> a -> a
trace string expr = unsafePerformIO $ do
    putTraceMsg string
    return expr
Run Code Online (Sandbox Code Playgroud)

所以在幕后有IO但是unsafePerformIO用来摆脱它.这是一个可能会破坏参照透明度的函数,你可以猜测它的类型IO a -> a和名称.


Dar*_*rio 17

trace简直是不纯洁的.IOmonad 的要点是保持纯度(类型系统没有注意到IO)并定义语句的执行顺序,否则通过惰性求值几乎不会定义.

然而,就自己的风险而言,你可以将一些攻击IO a -> a,即执行不纯的IO.这是一个黑客,当然"受到懒惰评估"的影响,但这就是跟踪只是为了调试而做的事情.

不过,您可能应该采用其他方式进行调试:

  1. 减少调试中间值的需要

    • 编写小型,可重用,清晰,通用的函数,其正确性是显而易见的.
    • 将正确的碎片组合成更正确的碎片.
    • 编写测试或以交互方式试用.
  2. 使用断点等(基于编译器的调试)

  3. 使用通用monad.如果你的代码是monadic,那么把它写成独立于具体的monad.使用type M a = ...而不是普通IO ....之后你可以通过变换器轻松组合monad并在它上面放置一个调试monad.即使monad的需求消失了,你也可以插入Identity a纯值.


C. *_*ann 14

对于它的价值,这里有两种"调试"问题:

  • 记录中间值,例如特定子表达式在每次调用时对递归函数的值
  • 检查表达式求值的运行时行为

在严格的命令式语言中,这些通常是一致的.在Haskell中,他们通常不会:

  • 记录中间值可以改变运行时行为,例如通过强制评估否则将被丢弃的术语.
  • 由于懒惰和共享的子表达式,实际的计算过程可能与表达式的表观结构有很大不同.

如果您只想保留中间值的日志,有很多方法可以做到 - 例如,不是将所有内容都解压缩IO,一个简单的Writermonad就足够了,这相当于让函数返回它们实际的2元组结果和累加器值(通常是某种列表).

通常也不需要将所有内容放入monad中,只需要写入"log"值的函数 - 例如,您可以只分解可能需要进行日志记录的子表达式,使主逻辑保持纯净,然后通过将常规方式的纯函数和记录计算与fmaps和whatnot 相结合来重新组装整体计算.请记住,Writer对于monad来说这是一个令人遗憾的借口:无法日志中读取,只能写入它,每个计算在逻辑上都与其上下文无关,这使得更容易处理周围的事情.

但在某些情况下,即使是过度杀戮 - 对于许多纯函数来说,只需将子表达式移动到顶层并在REPL中尝试一下就可以了.

但是,如果你想实际检查纯代码的运行时行为 - 例如,要找出子表达式偏离的原因 - 通常没有办法从其他纯代码中做到这一点 - 事实上,这实质上是纯度的定义.因此,在这种情况下,您别无选择,只能使用纯语言"外部"存在的工具:或者是不正确的函数,例如unsafePerformPrintfDebugging--errr,我的意思是 - trace或者修改后的运行时环境,例如GHCi调试器.