如何用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编程的正确方法)。