如何用printfs“debugging”Haskell?

来自Ocaml社区,我想学习一点Haskell。 转换进行得相当顺利,但我有点困惑与debugging。 我曾经在我的ocaml代码中join(大量的)“printf”来检查一些中间值,或者作为标记来查看计算完全失败的地方。

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

我也find跟踪function: http : //www.haskell.org/haskellwiki/Debugging#Printf_and_friends这似乎正是我想要的,但我不明白它的types:没有任何地方的IO ! 有人可以解释我跟踪function的行为吗?

trace是最容易使用的debugging方法。 这不是因为你指出的原因在IO :不需要在IO monad中解除你的代码。 它是这样实现的

 trace :: String -> a -> a trace string expr = unsafePerformIO $ do putTraceMsg string return expr 

所以在幕后有IO,但是使用unsafePerformIO来逃避它。 这是一个函数,可能会破坏参考透明度,你可以猜测它的typesIO a -> a ,也是它的名字。

trace简直是不纯的。 IO monad的意义在于保持纯度(没有被types系统忽略的IO)并且定义语句的执行顺序,否则通过懒惰的评估实际上是不确定的。

然而,对于自己的风险,你可以一起破解一些IO a -> a ,即执行不纯的IO。 这是一个黑客攻击,当然也会受到懒惰评估的“困扰”,但这只是为了debugging而做的。

尽pipe如此,你也许应该去其他的方式进行debugging:

1)减lessdebugging中间值的需要

  • 编写小巧,可重用,清晰的通用函数,其正确性显而易见。
  • 将正确的棋子组合成更大的棋子。
  • 编写testing或交互式地尝试片断

2)

  • 使用断点等(基于编译器的debugging)

3)

  • 使用通用单子。 如果你的代码是monadic的话,写一个独立于具体的monad。 使用type M a = ...而不是简单的IO ... 之后你可以很容易地通过变换器将monad组合起来,并在其上面放置一个debuggingmonad。 即使单子的需求消失了,你也可以插入Identity a作为纯粹的值。

对于什么是值得的,实际上有两种“debugging”在这里问题:

  • 将中间值(例如每个调用中特定子expression式的值)logging到recursion函数中
  • 检查expression式评估的运行时行为

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

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

如果你只是想保留一个中间值的日志,有很多方法可以做到这一点,例如,而不是将所有东西都提取到IO ,一个简单的Writer monad就足够了,这相当于使函数返回一个2元组他们的实际结果和累加器值(通常是某种列表)。

通常也不需要把所有东西都放到monad中,只需要写入“log”值的函数 – 例如,你可以只分解可能需要做日志logging的子expression式,保留主逻辑的纯粹性,然后重新组合整个计算结合纯函数和logging计算以通常的方式fmap和什么。 请记住, Writer是monad的一个抱歉的借口:没有办法日志中读取,只写信给它,每个计算在逻辑上独立于其上下文,这使得更容易处理周围的事情。

但是,在某些情况下,即使这样做是过度的 – 对于许多纯函数,只要将子expression式转移到顶层,然后在REPL中尝试就能很好地工作。

然而,如果你想要真正检查纯代码的运行时行为,例如,为了弄清楚为什么一个子expression式分歧 – 通常没有办法从其他纯代码中这样做 – 事实上,这基本上是纯度的定义 。 所以在这种情况下,你别无select,只能使用纯粹语言之外的工具:不纯的函数,比如unsafePerformPrintfDebugging –errr,我的意思是trace或者修改的运行时环境,比如GHCidebugging器。

trace也往往过分评估印刷的论点,在这个过程中失去了很多懒惰的好处。

如果在研究输出之前可以等到程序结束,那么堆叠Writer monad是实现logging器的经典方法。 我在这里使用这个从不纯的HDBC代码返回一个结果集。

那么,因为整个Haskell是build立在懒惰评估的原则之上的(所以计算的顺序实际上是非确定性的),使用printf几乎没有什么意义。

如果REPL +检查结果值对于debugging是不够的,那么把所有东西都包含进IO是唯一的select(但这不是Haskell编程的正确方法)。