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


当前回答

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

来自项目的Readme:

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: `dbg: (+ 10 20) => 30` dbg: (* 2 (dbg (+ 10 20))) => 60

其他回答

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

来自项目的Readme:

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: `dbg: (+ 10 20) => 30` dbg: (* 2 (dbg (+ 10 20))) => 60

我最喜欢的方法是在整个代码中自由地散布printlns。多亏了#_ reader宏,打开和关闭它们很容易(它使阅读器以下面的形式阅读,然后假装从未见过它)。或者你可以使用宏扩展为传入体或nil,这取决于一些特殊变量的值,例如*debug*:

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

这里有一个(def *debug* false),它将扩展为nil。如果是真的,它就会扩张到裹着do的身体。


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


Then there's something which is currently incompatible with swank-clojure's REPL, but is too good not to mention: debug-repl. You can use it in a standalone REPL, which is easy to get e.g. with Leiningen (lein repl); and if you're launching your programme from the command line, then it's going to bring its own REPL up right in your terminal. The idea is that you can drop the debug-repl macro in anywhere you like and have it bring up its own REPL when the programme's execution reaches that point, with all locals in scope etc. A couple of relevant links: The Clojure debug-repl, Clojure debug-repl tricks, how 'bout a debug-repl (on the Clojure Google group), debug-repl on Clojars.


swank-clojure在使SLIME的内置调试器在处理Clojure代码时发挥了足够的作用——注意stacktrace中不相关的部分是如何被灰色化的,因此很容易找到正在调试的代码中的实际问题。需要记住的一件事是,没有“名称标签”的匿名函数出现在堆栈跟踪中,基本上没有附加任何有用的信息;当添加“name标签”时,它确实会出现在stacktrace中,并且一切正常:

(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:

(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开始的地方:

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

我把这个插入我的用户。clj,所以在所有REPL会话中都可用。

使用spyscope实现一个自定义阅读器宏,这样你的调试代码也是生产代码 https://github.com/dgrnbrg/spyscope

我有一个调试宏,我觉得非常有用:

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

你可以把它插入到任何你想看发生了什么和什么时候:

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