在Clojure中进行调试?[关闭]


227

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


在增加的指引下,看到“调试工具和技术”的REPL指南中的答案:clojure.org/guides/repl/...
瓦伦丁Waeselynck

Answers:


158

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

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

产生输出:

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已移动:

您需要依赖项:

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

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

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

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

(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

2
很好,但是如何获取clojure来找到'clojure.contrib.trace?我在类路径上有clojure-contrib jar,但是REPL说user=> (use 'closure.contrib.trace) java.io.FileNotFoundException: Could not locate closure/contrib/trace__init.class or closure/contrib/trace.clj on classpath: (NO_SOURCE_FILE:0)
LarsH 2010年

2
您可能将Clojure误拼为闭包,还是评论中的错字?您可以加载其他clojure.contrib库吗?
约翰·劳伦斯·阿斯普登

12
从1.3版本开始,它已移至clojure.tools.trace(github.com/clojure/tools.trace
乔治

4
如果您收到:“IllegalStateException异常不能动态地结合非动态无功”在这里看到:stackoverflow.com/questions/8875353/...
科尼利厄斯

2
在1.5版本中也可以使用吗?我正在用clojure koans学习Clojure,但是还不能使dotrace正常工作。
2014年

100

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

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


4
更好的是:Spyscope
Zaz

@Zaz我完全同意。Spyscope很棒!甚至比调试器更好。当然是为了打字。
J Atkin

66

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

CIDER调试


46

我最喜欢的方法是println在代码中自由散布s… 由于有reader宏(很容易#_使读者以以下形式阅读,然后假装从未见过),因此打开和关闭它们很容易。或者,您可以使用宏扩展为传入的主体,也可以nil根据某些特殊变量的值进行扩展,例如*debug*

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

随着(def *debug* false)在那里,这将扩大到nil。使用时true,它会扩展到body包裹在一个do


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


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


swank-clojure可以很好地使SL​​IME的内置调试器在使用Clojure代码时发挥作用-请注意,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)
                   ^^^

5
实际上,现在有一个可以与swank一起使用的debug-repl版本:hugoduncan.org/post/2010/… ((扰流板警报:太棒了))

1
是的,在这里有一个链接很好,谢谢!同意很棒。:-)
米哈尔Marczyk

如果这是您的风格,那么您可能会喜欢后续响应中提到的debux库。github.com/philoskim/debux
Mallory-Erik

@ Mallory-Erik谢谢,我来看看!
米哈尔Marczyk

37

您还可以使用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)))

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


16

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

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

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

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


这在大多数情况下都是正确的,但是当您跨多个函数进行递归时,这并不是那么容易。
约翰·约翰·约翰(John John)

9

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

(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

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


9

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

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


我曾经成功地使用过La Clojure,但现在似乎死于草书github.com/JetBrains/la-clojure/blob/master/README.md
leeor

但是如何进行调试Leiningen,它显示:Error running 'ring server': Trampoline must be enabled for debugging
Gank

这似乎是特定于ringlein-也许值得提出一个单独的问题?
dskrvk

6

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

从项目的自述文件中

基本用法

这是一个简单的例子。宏dbg打印原始表单,并在REPL窗口上漂亮打印评估值。然后,它返回该值而不干扰代码执行。

如果用这样的dbg包装代码,

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

在REPL窗口中将打印以下内容。

REPL输出:

dbg: (+ 10 20) => 30

嵌套dbg

dbg宏可以嵌套。

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

REPL输出:

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

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





1

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

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

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


-4

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

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

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

(def-let '[a 1 b 2 c (atom 0)])
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.