Debuggen in Clojure? [geschlossen]

227

Was sind die besten Möglichkeiten, um Clojure-Code zu debuggen, während Sie den Repl verwenden?

Arun R.
quelle
Weitere Ergänzungen zu den folgenden Antworten finden Sie unter 'Debugging-Tools und -Techniken' im REPL-Handbuch: clojure.org/guides/repl/…
Valentin Waeselynck

Antworten:

158

Es gibt auch Dotrace, mit dem Sie die Ein- und Ausgänge ausgewählter Funktionen anzeigen können.

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

erzeugt die Ausgabe:

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

In Clojure 1.4 dotracehat sich bewegt:

Sie benötigen die Abhängigkeit:

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

Und Sie müssen der Funktionsdefinition die Dynamik ^: hinzufügen

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

Dann ist Bob wieder dein Onkel:

(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
John Lawrence Aspden
quelle
2
Schön, aber wie bringt man clojure dazu, 'clojure.contrib.trace zu finden? Ich habe das Clojure-Contrib-Glas auf meinem Klassenpfad, aber REPL sagtuser=> (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
2
Könnten Sie Clojure als Abschluss falsch schreiben, oder ist das ein Tippfehler im Kommentar? Können Sie andere clojure.contrib-Bibliotheken laden?
John Lawrence Aspden
12
Ab 1.3 wurde dies auf clojure.tools.trace ( github.com/clojure/tools.trace ) verschoben
George
4
Wenn Sie erhalten: "IllegalStateException kann nicht dynamische var nicht dynamisch binden" siehe hier: stackoverflow.com/questions/8875353/…
Cornelius
2
Funktioniert es auch in der Version 1.5? Ich lerne Clojure mit den Clojure-Koans, kann Dotrace aber noch nicht zum Laufen bringen.
nha
100

Ich habe ein kleines Debugging-Makro, das ich sehr nützlich finde:

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

Sie können es einfügen, wo immer Sie sehen möchten, was wann passiert:

;; 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)
John Lawrence Aspden
quelle
Sehr ähnlich clojure.tools.trace/trace.
Zaz
4
Noch besser: Spyscope .
Zaz
@Zaz Ich stimme vollkommen zu. Spyscope ist großartig! Vielleicht sogar besser als ein Debugger. Sicher zum Tippen.
J Atkin
66

Emacs 'CIDER hat einen Quell-Debugger, mit dem Sie Ausdruck für Ausdruck in einem Emacs-Puffer schrittweise vorgehen und sogar neue Werte einfügen können. Sie können alles darüber lesen Sie hier . Ein Demo-Screenshot:

CIDER-Debug

Amumu
quelle
46

Meine Lieblingsmethode ist das großzügige Streuen von printlns über den gesamten Code ... Das Ein- und Ausschalten ist dank des #_Lesemakros einfach (das den Leser in der folgenden Form lesen lässt und dann so tut, als hätte er es nie gesehen). Oder Sie können ein Makro verwenden, das entweder zu einem übergebenen Text erweitert wird oder nilvom Wert einer speziellen Variablen abhängt, z. B *debug*.:

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

Mit einem (def *debug* false)in dort wird dies erweitert nil. Mit truewird es erweitert, um bodyin ein eingewickelt do.


Die akzeptierte Antwort auf diese SO-Frage: Idiomatische Clojure für die Fortschrittsberichterstattung? ist sehr hilfreich beim Debuggen von Sequenzoperationen.


Dann gibt es etwas, das derzeit nicht mit der REPL von swank-clojure kompatibel ist , aber zu gut ist, um es nicht zu erwähnen : debug-repl. Sie können es in einer eigenständigen REPL verwenden, die zB mit Leiningen ( lein repl) leicht zu bekommen ist ; Wenn Sie Ihr Programm über die Befehlszeile starten, wird eine eigene REPL direkt in Ihrem Terminal angezeigt. Die Idee ist, dass Sie das debug-replMakro an einer beliebigen Stelle ablegen und es seine eigene REPL aufrufen lassen können, wenn die Ausführung des Programms diesen Punkt erreicht, mit allen Einheimischen im Geltungsbereich usw. Einige relevante Links: Das Clojure-Debug-Repl , das Clojure-Debug -repl Tricks , wie wäre es mit einer Debug-Repl (in der Clojure Google-Gruppe), Debug-Repl auf Clojars .


swank-clojure macht den integrierten SLIME-Debugger in angemessener Weise nützlich, wenn Sie mit Clojure-Code arbeiten. Beachten Sie, dass die irrelevanten Teile des Stacktraces ausgegraut sind, sodass das eigentliche Problem im zu debuggenden Code leicht zu finden ist. Beachten Sie, dass anonyme Funktionen ohne "Namensschilder" im Stacktrace angezeigt werden, an die im Grunde keine nützlichen Informationen angehängt sind. Wenn ein "Namensschild" hinzugefügt wird, erscheint es im Stacktrace und alles ist wieder in Ordnung:

(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)
                   ^^^
Michał Marczyk
quelle
5
Tatsächlich gibt es eine Version des Debug-Repl, die jetzt mit swank funktioniert: hugoduncan.org/post/2010/… (Spoiler-Warnung: es ist fantastisch)
1
Richtig, und es ist gut, hier einen Link zu haben, danke! Einverstanden über genial. :-)
Michał Marczyk
Wenn dies Ihr Stil ist, könnte Ihnen die in einer nachfolgenden Antwort erwähnte Debux-Bibliothek gefallen. github.com/philoskim/debux
Mallory-Erik
@ Mallory-Erik Danke, ich werde es überprüfen!
Michał Marczyk
37

Sie können auch Code einfügen, um sich mit Alex Osbornesdebug-repl in eine REPL mit allen lokalen Bindungen einzufügen :

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

Fügen Sie es dann an der Stelle ein, an der die Repl beginnen soll, um es zu verwenden:

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

Ich stecke dies in meine user.clj, damit es in allen REPL-Sitzungen verfügbar ist.

thnetos
quelle
16

"Beste Möglichkeiten zum Debuggen von Clojure-Code unter Verwendung der Repl"

Etwas linkes Feld, aber 'mit der REPL selbst'.

Ich schreibe seit über einem Jahr den Hobbyisten Clojure und habe kein großes Bedürfnis nach Debugging-Tools verspürt. Wenn Sie Ihre Funktionen klein halten und jede mit den erwarteten Eingaben an der REPL ausführen und die Ergebnisse beobachten, sollte es möglich sein, ein ziemlich klares Bild davon zu erhalten, wie sich Ihr Code verhält.

Ich finde, ein Debugger ist am nützlichsten, um STATE in einer laufenden Anwendung zu beobachten. Clojure macht es einfach (und macht Spaß!), In einem funktionalen Stil mit unveränderlichen Datenstrukturen zu schreiben (kein sich ändernder Zustand). Dies reduziert den Bedarf an einem Debugger massiv. Sobald ich weiß, dass sich alle Komponenten so verhalten, wie ich es erwartet habe (unter besonderer Berücksichtigung der Arten von Dingen), ist das Verhalten in großem Maßstab selten ein Problem.

Peter Westmacott
quelle
Dies ist meistens der Fall, aber wenn Sie beispielsweise eine Rekursion über mehrere Funktionen haben, ist dies nicht so einfach.
John
9

Wenn Sie Emacs / Slime / Swank verwenden, versuchen Sie dies bei der REPL:

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

(factorial 30)

Es gibt Ihnen keine vollständige Stapelverfolgung, wie Sie sie unter LISP erhalten würden, aber es ist gut zum Stöbern.

Dies ist die gute Arbeit von:

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

wie oben in einem Kommentar erwähnt.

John Lawrence Aspden
quelle
9

Für IntelliJ gibt es ein exzellentes Clojure-Plugin namens Cursive . Unter anderem bietet es eine REPL, die Sie im Debug-Modus ausführen und Ihren Clojure-Code genau wie bei Java durchlaufen können.

Ich würde die Antwort von Peter Westmacott unterstützen, da meiner Erfahrung nach das Ausführen von Teilen meines Codes in der REPL die meiste Zeit eine ausreichende Form des Debuggens ist.

dskrvk
quelle
Ich war mit La Clojure mit Erfolg, scheint aber für kursiven jetzt zu sterben github.com/JetBrains/la-clojure/blob/master/README.md
leeor
Aber wie man damit LeiningenError running 'ring server': Trampoline must be enabled for debugging
debuggt
Das scheint spezifisch zu sein ringoder lein- vielleicht lohnt es sich, eine separate Frage zu stellen?
dskrvk
6

Ab 2016 können Sie Debux verwenden , eine einfache Debugging-Bibliothek für Clojure / Script, die sowohl mit Ihrem Repl als auch mit der Konsole Ihres Browsers zusammenarbeitet. Sie können Makros in Ihren Code streuen dbg(debuggen) oder clog(console.log) und auf einfache Weise die Ergebnisse einzelner Funktionen usw. beobachten, die auf Ihrer REPL und / oder Konsole gedruckt sind.

Aus der Readme - Datei des Projekts :

Grundlegende Verwendung

Dies ist ein einfaches Beispiel. Das Makro dbg druckt ein Originalformular und druckt den ausgewerteten Wert im REPL-Fenster hübsch aus. Dann wird der Wert zurückgegeben, ohne die Codeausführung zu beeinträchtigen.

Wenn Sie den Code wie folgt mit dbg umschließen,

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

Folgendes wird im REPL-Fenster gedruckt.

REPL-Ausgabe:

dbg: (+ 10 20) => 30

Verschachtelte Datenbank

Das dbg-Makro kann verschachtelt werden.

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

REPL-Ausgabe:

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

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

Mallory-Erik
quelle
5

Hugo Duncan und seine Mitarbeiter leisten weiterhin hervorragende Arbeit mit dem Ritz- Projekt. Ritz-nrepl ist ein nREPL-Server mit Debug-Funktionen. Sehen Sie sich Hugos Debugger in Clojure- Vorträgen auf der Clojure / Conj 2012 an, um sie in Aktion zu sehen. Im Video sind einige der Folien nicht lesbar, daher möchten Sie die Folien möglicherweise von hier aus anzeigen .

Rodrigo Taboada
quelle
1

Ich komme aus Java und bin mit Eclipse vertraut. Mir gefällt, was Counterclockwise (das Eclipse-Plugin für die Clojure-Entwicklung) zu bieten hat: http://doc.ccw-ide.org/documentation.html#_debug_clojure_code

Yotsov
quelle
Keine ordnungsgemäße Haltepunktunterstützung. Fäden sind schwer zu töten.
CodeFarmer
1

Hier ist ein schönes Makro zum Debuggen komplizierter letFormulare:

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

... und ein Aufsatz, der seine Verwendung erklärt .

Jouni K. Seppänen
quelle
-4

Funktionsversion von def-let, die aus einem let eine Reihe von defs macht. Ein bisschen Kredit geht hierher

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

Verwendung: Muss den Inhalt mit einem Zitat zitieren, z

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