Clojure中如何打印`let`绑定的值?

5

如何在let绑定内部打印值?

当我开始使用Clojure进行开发时,我会在REPL中编写代码,然后将其转换为简单的let表达式。作为初学者,在这个(简单)转换阶段经常会犯错误。

(let [a (aFn ...)
       b (bFn ... a)]
   ;; error above
)

所以我会将它转换回类似的形式,基本上是将事物内联起来:
(println "a is"    (aFn ...))
(println "b is" (bFn ... (aFn ...)))
(let [a (aFn ...)
       b (bFn ... a)]
   ;; ...
)

多亏了Clojure的良好特性(不可变性,引用透明等),这个功能大部分时间都是有效的。

现在我要做的事情差不多是:

(let [a (aFn ...)
       _ (println "a is" a)
       b (bFn ... a)
      _ (println "b is" b)]
   ;; ...
)

这是一种改进,但仍然感觉笨拙。有没有更好的方法?


1
你真正需要的是一个调试器。如果你使用Cursive,你就有了一个。不过,你可以做的是spy本地绑定:http://brownsofa.org/blog/2014/08/03/debugging-in-clojure-tools/ - ClojureMostly
@Andre 你可能是对的。在REPL中原型化和让它工作,然后再次调试似乎很愚蠢,所以我想可能还有其他方法。 - nha
1
我经常这样做,但除非它进入生产环境,否则我不会感到困扰。使用打印语句进行调试已经很笨拙了,在let内部需要额外的下划线在我看来也没有太大区别。 - Alex
2个回答

3
您可以定义一个打印函数,该函数返回其参数:
(defn cl-print [x] (doto x (print)))

然后,只需要将您的表达式包装起来即可:
(let [a (cl-print (aFn ...))
      b (cl-print (bFn ... a))]
   ...)

3
我有一个完全不同的方法。我从不在我的let绑定中放置打印语句。我也认为你需要小心地调用函数,只是为了得到调试目的的值。虽然我们希望所有的函数都是无副作用的,但并非总是如此,因此调用函数只是为了获取要打印的值可能会产生意想不到的结果。还存在打印值如何影响惰性和实现惰性序列等问题。
我的方法是定义一些调试函数,将它们放在一个“debug”命名空间中。然后在函数体内需要时调用这些调试函数,而不是在let绑定部分调用。通常,我也定义一个debug-level变量,以便可以控制调试详细程度。这使我能够更改一个变量并增加或减少记录/打印的信息量。
我尝试过使用“聪明”的宏来使调试更容易 - 但老实说,这些通常需要更多的精力才能正确地完成所提供的好处。
我喜欢将我的调试函数放在一个单独的命名空间中,因为这可以帮助我确保我没有在生产版本中留下任何调试代码 - 或者它允许我在那里留下调试语句,但通过设置适当的调试级别来让它们“不做任何事情”。
正如另一篇文章所提到的,使用调试器可以消除/减少需要这些打印语句或调试函数的必要性。然而,我认为调试器也可能是一个双刃剑。太多时候,人们陷入坏的调试习惯中,他们依赖于跟踪和检查而不是思考和分析正在发生的事情。这可能会导致开发过程受太多的试错驱动,而不是足够的分析和理解。
你可以从一些简单的东西开始。
(def debug-level 20)

(defn debug [lvl prefix val]
  (if (>= lvl debug-level)
    (println (str prefix ": " val)))

(defn debug1 [prefix v]
  (debug 10 prefix v))

(defn debug2 [prefix v]
  (debug 20 prefix v))

等等,只需调用

然后只需调用即可

(debug2 :a a)

在您的函数体中,当调试级别为20或更高时,将打印出a的值。


你提出了很好的观点,但这涉及到切换到调试所调用的函数内部,我觉得这很烦人。我以前也在其他语言中使用过这样的函数,但不知怎么的,这次没有想到。 - nha

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接