在Clojure中进行调试?

Debugging in Clojure?

使用repl时,调试Clojure代码的最佳方法是什么?


还有dotrace,它使您可以查看选定函数的输入和输出。

1
2
3
(use 'clojure.contrib.trace)
(defn fib[n] (if (< n 2) n (+ (fib (- n 1)) (fib (- n 2)))))
(dotrace [fib] (fib 3))

产生输出:

1
2
3
4
5
6
7
8
9
10
11
TRACE t4425: (fib 3)
TRACE t4426: |    (fib 2)
TRACE t4427: |    |    (fib 1)
TRACE t4427: |    |    => 1
TRACE t4428: |    |    (fib 0)
TRACE t4428: |    |    => 0
TRACE t4426: |    => 1
TRACE t4429: |    (fib 1)
TRACE t4429: |    => 1
TRACE t4425: => 2
2

在Clojure 1.4中,dotrace已移动:

您需要依赖项:

1
2
[org.clojure/tools.trace"0.7.9"]
(require 'clojure.tools.trace)

并且您需要在函数定义中添加^:dynamic

1
(defn ^:dynamic fib[n] (if (< n 2) n (+ (fib (- n 1)) (fib (- n 2)))))

然后鲍勃再次成为你的叔叔:

1
2
3
4
5
6
7
8
9
10
11
12
(clojure.tools.trace/dotrace [fib] (fib 3))

TRACE t4328: (fib 3)
TRACE t4329: | (fib 2)
TRACE t4330: | | (fib 1)
TRACE t4330: | | => 1
TRACE t4331: | | (fib 0)
TRACE t4331: | | => 0
TRACE t4329: | => 1
TRACE t4332: | (fib 1)
TRACE t4332: | => 1
TRACE t4328: => 2


我有一个小的调试宏,我发现它非常有用:

1
2
;;debugging parts of expressions
(defmacro dbg[x] `(let [x# ~x] (println"dbg:" '~x"=" x#) x#))

您可以将其插入任何想看发生什么情况的时间以及何时:

1
2
3
4
5
6
7
8
9
10
11
;; Examples of dbg
(println (+ (* 2 3) (dbg (* 8 9))))
(println (dbg (println"yo")))
(defn factorial[n] (if (= n 0) 1 (* n (dbg (factorial (dec n))))))
(factorial 8)

(def integers (iterate inc 0))
(def squares  (map #(dbg(* % %))   integers))
(def cubes    (map #(dbg(* %1 %2)) integers squares))
(take 5 cubes)
(take 5 cubes)


Emacs的CIDER提供了一个源调试器,您可以在Emacs缓冲区内逐个表达式地表达表达式,甚至可以注入新值。你可以在这里读到所有和它有关的。演示屏幕截图:

CIDER debug


我最喜欢的方法是在代码中随意散布println ...由于#_ reader宏(使读者以以下形式阅读,然后假装从未见过),打开和关闭它们很容易。它)。或者,您可以使用宏,将其扩展为传入的主体或nil,具体取决于某些特殊变量(例如*debug*)的值:

1
2
3
(defmacro debug-do [& body]
  (when *debug*
    `(do ~@body)))

带有(def *debug* false)的地方,它将扩展为nil。使用true,它将扩展为包裹在do中的body

对此问题的公认答案:用于进度报告的惯用Clojure?在调试序列操作时非常有用。

然后有一些当前与swank-clojure的REPL不兼容的东西,但太好了,更不用说了:debug-repl。您可以在独立的REPL中使用它,例如,与Leiningen(lein repl);如果您要从命令行启动程序,那么它将在您的终端中直接启动自己的REPL。这个想法是,您可以将debug-repl宏放在您喜欢的任何位置,并在程序执行到该点时弹出自己的REPL,所有本地变量都在作用域中。几个相关链接:Clojure debug-repl ,Clojure debug-repl技巧,如何调试debug-repl(在Clojure Google组中),clojars上的debug-repl。

swank-clojure可以很好地使SL??IME的内置调试器在使用Clojure代码时发挥作用-请注意,stacktrace的无关位是如何变灰的,因此很容易在被调试的代码中找到实际的问题。要记住的一件事是,没有"名称标签"的匿名函数出现在堆栈跟踪中,基本上没有附加有用的信息;当添加"名称标签"时,它确实出现在堆栈跟踪中,并且一切都很好:

1
2
3
4
5
6
7
8
9
(fn [& args] ...)
vs.
(fn tag [& args] ...)

example stacktrace entries:
1: user$eval__3130$fn__3131.invoke(NO_SOURCE_FILE:1)
vs.                ^^
1: user$eval__3138$tag__3139.invoke(NO_SOURCE_FILE:1)
                   ^^^


您还可以使用Alex Osborne的debug-repl插入代码,以使用所有本地绑定将自己放入REPL:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
(defmacro local-bindings
 "Produces a map of the names of local bindings to their values."
  []
  (let [symbols (map key @clojure.lang.Compiler/LOCAL_ENV)]
    (zipmap (map (fn [sym] `(quote ~sym)) symbols) symbols)))

(declare *locals*)
(defn eval-with-locals
 "Evals a form with given locals. The locals should be a map of symbols to
values."
  [locals form]
  (binding [*locals* locals]
    (eval
     `(let ~(vec (mapcat #(list % `(*locals* '~%)) (keys locals)))
        ~form))))

(defmacro debug-repl
 "Starts a REPL with the local bindings available."
  []
  `(clojure.main/repl
    :prompt #(print"dr =>")
    :eval (partial eval-with-locals (local-bindings))))

然后使用它,将其插入到您希望REPL开始的任何地方:

1
2
3
(defn my-function [a b c]
  (let [d (some-calc)]
    (debug-repl)))

我将此粘贴在user.clj中,以便在所有REPL会话中都可用。


"使用REPL时调试Clojure代码的最佳方法"

略微偏左,但"使用REPL本身"。

我已经写了业余爱好者Clojure一年多了,对任何调试工具的需求都没有。如果您使函数保持较小,并在REPL上使用预期的输入运行每个函数并观察结果,那么应该可以很清楚地了解代码的行为方式。

我发现调试器对于观察正在运行的应用程序中的STATE最有用。 Clojure使使用具有不变数据结构(无变化状态)的功能样式编写起来容易(有趣)!这大大减少了对调试器的需求。一旦我知道所有组件的行为都符合我的预期(要特别注意事物的类型),那么大规模行为就很少有问题了。


对于IntelliJ,有一个出色的Clojure插件称为Cursive。除其他外,它提供了一个REPL,您可以在调试模式下运行该REPL并逐步执行Clojure代码,就像您进行例如Java。

尽管我认为彼得·韦斯特马科特(Peter Westmacott)的回答是第二位的,但根据我的经验,在大多数情况下,仅在REPL中运行我的代码片段是一种足够的调试形式。


如果您使用emacs / slime / swank,请在REPL上尝试以下操作:

1
2
3
4
5
6
(defn factorial [n]
        (cond (< n 2) n
              (= n 23) (swank.core/break)
              :else (* n (factorial (dec n)))))

(factorial 30)

它不会像在LISP下那样为您提供完整的堆栈跟踪信息,但是它对戳戳非常有用
周围。

这是以下方面的优良工作:

http://hugoduncan.org/post/2010/swank_clojure_gets_a_break_with_the_local_environment.xhtml

正如上面评论中提到的那样。


从2016年开始,您可以使用Debux,这是Clojure / Script的简单调试库,可与您的repl以及浏览器的控制台配合使用。您可以在代码中添加dbg(调试)或clog(console.log)宏,并轻松观察打印到REPL和/或控制台的各个函数的结果等。

从项目的自述文件中:

Basic usage

This is a simple example. The macro dbg prints an original form and
pretty-prints the evaluated value on the REPL window. Then it returns
the value without interfering with the code execution.

If you wrap the code with dbg like this,

(* 2 (dbg (+ 10 20))) ; => 60

the following will be printed in the
REPL window.

REPL output:

dbg: (+ 10 20) => 30

Nested dbg

The dbg macro can be nested.

(dbg (* 2 (dbg (+ 10 20)))) ; => 60

REPL output:

1
`dbg: (+ 10 20) => 30`

dbg: (* 2 (dbg (+ 10 20))) => 60


雨果·邓肯(Hugo Duncan)和合作者继续在Ritz项目中做出色的工作。 Ritz-nrepl是具有调试功能的nREPL服务器。在Clojure / Conj 2012上观看Hugo的Clojure中的Debuggers演讲,观看它的运行,在视频中一些幻灯片不可读,因此您可能希望从此处查看幻灯片。


使用实现自定义阅读器宏的spyscope,以便您的调试代码也是生产代码
https://github.com/dgrnbrg/spyscope


这是一个调试复杂的let表单的好宏:

1
2
3
4
5
6
7
8
(defmacro def+
 "def with binding (def+ [{:keys [a b d]} {:a 1 :b 2 :d 3}])"
  [bindings]
  (let [let-expr (macroexpand `(let ~bindings))
        vars (filter #(not (.contains (str %)"__"))
               (map first (partition 2 (second let-expr))))
        def-vars (map (fn [v] `(def ~v ~v)) vars)]
    (concat let-expr def-vars)))

...以及一篇说明其用途的文章。


来自Java并熟悉Eclipse,我喜欢逆时针(Clojure开发的Eclipse插件)所提供的功能:http://doc.ccw-ide.org/documentation.html#_debug_clojure_code


def-let的函数版本,它将let转换为一系列defs。功劳归功于这里

1
2
3
4
5
6
7
8
9
10
11
(defn def-let [aVec]
  (if-not (even? (count aVec))
    aVec
    (let [aKey (atom"")      
          counter (atom 0)]
      (doseq [item aVec]
        (if (even? @counter)
          (reset! aKey  item)          
          (intern *ns*  (symbol @aKey)  (eval item)))
        ;   (prn  item)      
    (swap! counter inc)))))

用法:需要用引号将内容引号,例如

1
(def-let '[a 1 b 2 c (atom 0)])