我看到很多资源强烈鼓励程序员不要使用生产代码中的Debug.Trace
,因为缺乏引用透明度。我仍然不能完全理解这个问题,但似乎无法找到根本原因。
我的理解是跟踪不能改变任何表达式的输出(尽管在某些情况下它可能导致表达式以不同的顺序执行,或者导致表达式被评估,否则懒得跳过)。因此跟踪不能影响纯函数的输出。它不会抛出任何错误。那为什么它如此强烈地反对呢? (如果我上面的任何假设都不正确,请指出!)。它只是一个哲学辩论,还是一个表演的事情,还是它真的能以某种方式引入一个错误?
当使用其他不太严格的语言进行编程时,我常常发现有兴趣的生产应用程序日志值有助于诊断我无法在本地重现的问题。
答案 0 :(得分:10)
当然trace foo bar
可以抛出比bar
更多的错误:它可以(将)抛出任何错误foo
抛出!
但这并不是避免它的理由。真正的原因是您通常希望程序员能够控制输出发生的顺序。你不希望你的调试语句说'#34; Foo正在发生!"打断自己,然后说'#34; Foo就是hapBar正在发生!写作!",例如;并且你不希望潜在的调试声明因为它所包含的值永远不会被需要而无法打印。控制该订单的正确方法是承认您正在执行IO
并在您的类型中反映出来。
答案 1 :(得分:6)
我经常发现有兴趣的生产应用程序日志值有助于诊断我无法在本地重现的问题。
这可以很好。例如,GHC可以使用打开各种跟踪功能的选项运行。生成的日志可用于调查错误。也就是说,“纯”代码输出的狂野性质可能会使各种事件处于合理的顺序中变得有点困难。例如,-ddump-inlinings
和-ddump-rule-rewrites
输出可以与其他输出交错。因此开发人员可以方便地使用更“纯粹”的代码,但代价是更难以通过的日志。这是一种权衡。
答案 2 :(得分:4)
Debug.Trace.trace
打破了引用透明度。
let x = e in x+x
这样的表达式,通过参考透明度,必须等同于e+e
,无论e
是什么。
然而,
let x = trace "x computed!" 12
in x+x
将(可能)打印调试消息一次,而
trace "x computed!" 12 + trace "x computed!" 12
将(可能)打印调试消息两次。这不应该发生。
唯一务实的方法是将输出看作是我们不应该依赖的副作用"。我们已经在纯代码中做到了这一点:我们忽视了可观察的副作用"如经过的时间,用过的空间,消耗的能量。实际上,我们不应该依赖表达式在评估期间消耗1324
个字节,并且编写代码,在新编译器能够优化更多并保存2
个字节之后中断。同样,生产代码永远不应该依赖于trace
消息的存在。
(上面,我写了#34;可能"因为这是我认为GHC目前所做的,但原则上另一个编译器可以通过内联let x=e in ...
来优化e
,这会导致多条trace
条消息。)