Clojure在'let'绑定中打印值

Clojure在'let'绑定中打印值,clojure,Clojure,在let绑定中打印值的惯用方法是什么 当我开始在Clojure中开发时,我在REPL中编写代码,然后将其转换为简单的let表达式。作为一个初学者,我经常在这个(简单的)转换阶段犯错误 (let [a (aFn ...) b (bFn ... a)] ;; error above ) 所以我会把它转换回类似的东西,基本上是内联的东西: (println "a is" (aFn ...)) (println "b is" (bFn ... (aFn ...))) (let

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)]
   ;; ...
)

这是一个进步,但仍然感觉笨拙。执行此操作的正确方法是什么?

您可以定义一个返回其参数的打印函数:

(defn cl-print [x] (doto x (print)))
那么,这只是一个包装你的表达的问题:

(let [a (cl-print (aFn ...))
      b (cl-print (bFn ... a))]
   ...)

我倾向于采取完全不同的方法。我从不在let绑定中放入打印语句。我还认为,在调用函数只是为了获得调试所需的值时,需要小心。虽然我们希望所有函数都没有副作用,但情况并非总是如此,因此调用function只是为了获得要打印的值可能会产生意外的结果。还有一个问题是打印值如何影响惰性和惰性序列的实现等

我的方法是定义一些调试函数,我将它们放在“debug”名称空间中。然后,我在需要时从函数体内部调用这些调试函数,而不是在let绑定部分。通常,我还定义了一个调试级别变量,以便能够控制调试的详细程度。这允许我更改一个var并增加或减少记录/打印的信息量

我曾尝试过使用“聪明”的宏来简化调试,但老实说,这些宏通常比它们所提供的好处更需要努力

我喜欢将调试函数放在一个单独的名称空间中,因为这有助于确保我的生产版本中没有留下任何调试代码,或者允许我将调试语句留在其中,但通过设置适当的调试级别使它们“什么也不做”

正如另一篇文章所提到的,使用调试器可以消除/减少对这些打印语句或调试函数的需要。然而,我认为调试器也是一把双刃剑。人们经常陷入糟糕的调试状态,依赖于跟踪和检查,而不是思考和分析到底发生了什么。这可能倾向于由太多的尝试和错误以及不够的分析和理解驱动的开发

你可以从以下简单的事情开始

(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的值

你真正想要的是一个调试器。如果你用草书,你就有一个。不过,您可以做的是
spy
本地绑定:@Andre您可能是对的。Is觉得在REPL中制作原型并使其工作很愚蠢,然后不得不再次调试它,所以我认为可能还有其他东西。我一直在这样做,但除非它投入生产,否则它不会打扰我。使用print语句进行调试已经很笨拙了,let中需要的额外下划线对IMHO没有什么影响。您的观点很好,但是这涉及到切换到调用调试的函数的内部,我觉得这很烦人。我以前也在其他语言中使用过这样的函数,但不知怎么的,这次我没有想到。