Debug in Clojure? [chiuso]


227

Quali sono i modi migliori per eseguire il debug del codice Clojure mentre si utilizza il sostituto?


Oltre alle risposte di seguito, consultare "Strumenti e tecniche di debug" nella guida REPL: clojure.org/guides/repl/…
Valentin Waeselynck

Risposte:


158

C'è anche dotrace, che consente di esaminare gli ingressi e le uscite delle funzioni selezionate.

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

produce l'output:

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, dotraceè stato spostato:

Hai bisogno della dipendenza:

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

E devi aggiungere ^: dinamico alla definizione della funzione

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

Quindi Bob è di nuovo tuo zio:

(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
Bello, ma come si ottiene il clojure per trovare 'clojure.contrib.trace? Ho il vaso clojure-contrib sul mio percorso di classe ma REPL diceuser=> (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
Potresti scrivere erroneamente il clojure come chiusura o è un errore di battitura nel commento? Puoi caricare altre librerie clojure.contrib?
John Lawrence Aspden,

12
A partire da 1.3 questo è stato spostato su clojure.tools.trace ( github.com/clojure/tools.trace )
George

4
Se stai ottenendo: "IllegalStateException non può associare dinamicamente var non dinamici" vedi qui: stackoverflow.com/questions/8875353/…
Cornelius,

2
Funziona anche nella versione 1.5? Sto imparando Clojure con i koan clojure, ma non riesco ancora a far funzionare dotrace.
nha

100

Ho una piccola macro di debug che trovo molto utile:

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

Puoi inserirlo ovunque tu voglia vedere cosa sta succedendo e quando:

;; 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
Ancora meglio: Spyscope .
Zaz,

@Zaz Sono completamente d'accordo. Spyscope è fantastico! Ancora meglio forse di un debugger. Certamente per scrivere.
J Atkin,

66

Il CIDER di Emacs ha ottenuto un debugger di origine che è possibile passare da un'espressione all'altra all'interno di un buffer Emacs e persino iniettare nuovi valori. Puoi leggere tutto al riguardo qui . Uno screenshot della demo:

Debug CIDER


46

Il mio metodo preferito è una spolverata liberale di printlns in tutto il codice ... Accenderli e spegnerli è facile grazie alla #_macro del lettore (che fa leggere il lettore nel seguente modulo, quindi finge di non averlo mai visto). Oppure potresti utilizzare una macro che si espande in un corpo passato o in nilbase al valore di una variabile speciale, ad esempio *debug*:

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

Con un (def *debug* false)in là, questo si espanderà a nil. Con true, si espanderà in bodyavvolto in a do.


La risposta accettata a questa domanda SO: Idiomatic Clojure per i rapporti sui progressi? è molto utile durante il debug delle operazioni di sequenza.


Poi c'è qualcosa che è attualmente incompatibile con Swank-clojure 's REPL, ma è troppo bello per non parlare di: debug-repl. Puoi usarlo in un REPL autonomo, che è facile da ottenere ad es. Con Leiningen ( lein repl); e se stai lanciando il tuo programma dalla riga di comando, allora porterà il suo REPL proprio nel tuo terminale. L'idea è che puoi rilasciare la debug-replmacro ovunque tu voglia e far apparire il proprio REPL quando l'esecuzione del programma raggiunge quel punto, con tutti i locali in ambito ecc. Un paio di collegamenti rilevanti: il debug-sostitu di Clojure , il debug di Clojure -repl trucchi , che ne dici di un debug-repl (sul gruppo Clojure Google), debug-repl su Clojars .


swank-clojure fa un lavoro adeguato nel rendere utile il debugger integrato di SLIME quando si lavora con il codice Clojure - notare come i bit irrilevanti dello stacktrace siano disattivati, quindi è facile trovare l'effettivo problema nel codice in fase di debug. Una cosa da tenere a mente è che le funzioni anonime senza "etichette dei nomi" appaiono nello stacktrace senza praticamente alcuna informazione utile allegata; quando viene aggiunto un "nome tag", appare nello stacktrace e tutto va di nuovo bene:

(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
In realtà c'è una versione del debug-repl che funziona ora con swank : hugoduncan.org/post/2010/… (avviso spoiler: è fantastico)

1
Bene, ed è bello avere un link qui, grazie! D'accordo sul fantastico. :-)
Michał Marczyk,

Se questo è il tuo stile, potresti apprezzare la libreria debux menzionata in una risposta successiva. github.com/philoskim/debux
Mallory-Erik

@ Mallory-Erik Grazie, lo controllerò!
Michał Marczyk,

37

Puoi anche inserire il codice per rilasciarti in un REPL con tutti i binding locali, usando Alex Osbornedebug-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))))

Quindi per usarlo, inseriscilo ovunque tu voglia avviare il sostituto:

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

Lo inserisco nel mio user.clj in modo che sia disponibile in tutte le sessioni REPL.


16

"modi migliori per eseguire il debug del codice Clojure, mentre si usa il sost

Leggermente a sinistra, ma "usando la REPL stessa".

Ho scritto l'hobbista Clojure per oltre un anno e non ho sentito il bisogno di strumenti di debug. Se mantieni piccole le tue funzioni ed esegui ognuna con gli input previsti nel REPL e osservi i risultati, dovrebbe essere possibile avere un quadro abbastanza chiaro di come si sta comportando il tuo codice.

Trovo che un debugger sia molto utile per osservare STATE in un'applicazione in esecuzione. Clojure rende semplice (e divertente!) Scrivere in uno stile funzionale con strutture di dati immutabili (senza cambiare stato). Ciò riduce enormemente la necessità di un debugger. Una volta che so che tutti i componenti si comportano come mi aspetto (prestando particolare attenzione ai tipi di cose), il comportamento su larga scala è raramente un problema.


Questo è per lo più vero, ma quando si ha la ricorsione in più funzioni, ad esempio, non è così facile.
Giovanni,


9

Per IntelliJ c'è un eccellente plug-in Clojure chiamato Cursive . Tra le altre cose, fornisce un REPL che puoi eseguire in modalità debug e scorrere il tuo codice Clojure proprio come faresti per esempio con Java.

Vorrei secondare la risposta di Peter Westmacott anche se, nella mia esperienza, l'esecuzione di pezzi del mio codice nel REPL è il più delle volte una forma sufficiente di debug.


Stavo usando La Clojure con successo, ma sembra che stia morendo in favore del corsivo ora github.com/JetBrains/la-clojure/blob/master/README.md
leeor

Ma come eseguire il debug Leiningen, mostra:Error running 'ring server': Trampoline must be enabled for debugging
Gank

Questo sembra essere specifico ringo leinforse vale la pena pubblicare una domanda separata?
dskrvk,

6

A partire dal 2016 è possibile utilizzare Debux , una semplice libreria di debug per Clojure / Script che funziona in combinazione con il tuo sostituto e la console del tuo browser. È possibile cospargere macro dbg(debug) o clog(console.log) nel codice e osservare facilmente i risultati di singole funzioni, ecc., Stampati su REPL e / o console.

Dal file Leggimi del progetto :

Utilizzo di base

Questo è un semplice esempio. La macro dbg stampa un modulo originale e stampa graziosamente il valore valutato nella finestra REPL. Quindi restituisce il valore senza interferire con l'esecuzione del codice.

Se avvolgi il codice con dbg in questo modo,

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

quanto segue verrà stampato nella finestra REPL.

Uscita REPL:

dbg: (+ 10 20) => 30

Dbg nidificato

La macro dbg può essere nidificata.

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

Uscita REPL:

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

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


5

Hugo Duncan e collaboratori continuano a fare un lavoro straordinario con il progetto ritz . Ritz-nrepl è un server nREPL con funzionalità di debug. Guarda i debugger di Hugo in Clojure parlare a Clojure / Conj 2012 per vederlo in azione, nel video alcune diapositive non sono leggibili, quindi potresti voler vedere le diapositive da qui .




1

Ecco una bella macro per il debug di letmoduli complicati :

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

... e un saggio che spiega il suo uso .


-4

Versione della funzione di def-let, che trasforma un let in una serie di def. Un po 'di credito va a qui

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

Utilizzo: deve citare il contenuto con un preventivo, ad es

(def-let '[a 1 b 2 c (atom 0)])
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.