Depuración en Clojure? [cerrado]


227

¿Cuáles son las mejores formas de depurar el código Clojure, mientras se usa la respuesta?


Además de las respuestas a continuación, consulte 'Herramientas y técnicas de depuración' en la guía REPL: clojure.org/guides/repl/…
Valentin Waeselynck

Respuestas:


158

También hay dotrace, que le permite ver las entradas y salidas de las funciones seleccionadas.

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

produce la salida:

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

En Clojure 1.4, se dotraceha movido:

Necesitas la dependencia:

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

Y necesita agregar ^: dinámica a la definición de la función

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

Entonces Bob es una vez más tu tío:

(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
Bien, pero ¿cómo se obtiene clojure para encontrar 'clojure.contrib.trace? Tengo el jar clojure-contrib en mi classpath pero 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
¿Podría estar escribiendo incorrectamente clojure como cierre, o es un error tipográfico en el comentario? ¿Se pueden cargar otras bibliotecas clojure.contrib?
John Lawrence Aspden

12
A partir de 1.3, esto se ha movido a clojure.tools.trace ( github.com/clojure/tools.trace )
George

44
Si obtiene: "IllegalStateException no puede enlazar dinámicamente var no dinámicas", consulte aquí: stackoverflow.com/questions/8875353/…
Cornelius

2
¿Funciona también en la versión 1.5? Estoy aprendiendo Clojure con los clojure koans, pero todavía no puedo hacer que dotrace funcione.
nha

100

Tengo una pequeña macro de depuración que me parece muy útil:

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

Puede insertarlo donde quiera ver lo que sucede y cuándo:

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


44
Aún mejor: Spyscope .
Zaz

@Zaz estoy totalmente de acuerdo. ¡Spyscope es asombroso! Incluso mejor que un depurador. Ciertamente para escribir.
J Atkin

66

El CIDER de Emacs tiene un depurador de origen que puede pasar expresión por expresión dentro de un búfer de Emacs e incluso inyectar nuevos valores. Puedes leer todo sobre esto aquí . Una captura de pantalla de demostración:

CIDER depuración


46

Mi método favorito es una pizca liberal de printlns en todo el código ... Activarlos y desactivarlos es fácil gracias a la #_macro del lector (que hace que el lector lea de la siguiente forma, y ​​luego finja que nunca lo ha visto). O podría usar una macro expandiéndose a un cuerpo pasado o nildependiendo del valor de alguna variable especial, por ejemplo *debug*:

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

Con un (def *debug* false)allí, esto se expandirá a nil. Con true, se expandirá a bodyenvuelto en un do.


La respuesta aceptada a esta pregunta SO: ¿ Idiomatic Clojure para informes de progreso? es muy útil al depurar operaciones de secuencia.


Entonces hay algo que es actualmente incompatible con Swank-clojure 's REPL, pero es demasiado bueno para no mencionar: debug-repl. Puede usarlo en un REPL independiente, que es fácil de obtener, por ejemplo, con Leiningen ( lein repl); y si está iniciando su programa desde la línea de comando, entonces traerá su propia REPL directamente en su terminal. La idea es que puede soltar la debug-replmacro en cualquier lugar que desee y hacer que aparezca su propio REPL cuando la ejecución del programa llegue a ese punto, con todos los locales en el alcance, etc. Un par de enlaces relevantes: Clojure debug-repl , Clojure debug -repl trucos , qué tal un debug-repl (en el grupo Clojure Google), debug-repl en Clojars .


swank-clojure hace un trabajo adecuado al hacer que el depurador incorporado de SLIME sea útil cuando se trabaja con código Clojure; tenga en cuenta cómo los bits irrelevantes del stacktrace están atenuados para que sea fácil encontrar el problema real en el código que se está depurando. Una cosa a tener en cuenta es que las funciones anónimas sin "etiquetas de nombre" aparecen en el stacktrace básicamente sin información útil adjunta; cuando se agrega una "etiqueta de nombre", aparece en el stacktrace y todo vuelve a estar bien:

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

55
En realidad, hay una versión de la respuesta de depuración que ahora funciona con swank: hugoduncan.org/post/2010/… (Alerta de spoiler: es increíble)

1
Bien, y es bueno tener un enlace aquí, ¡gracias! De acuerdo en increíble. :-)
Michał Marczyk

Si este es su estilo, es posible que le guste la biblioteca de Debux mencionada en una respuesta posterior. github.com/philoskim/debux
Mallory-Erik

@ Mallory-Erik ¡Gracias, lo comprobaré!
Michał Marczyk

37

También puede insertar código para dejarse caer en un REPL con todos los enlaces locales, utilizando 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))))

Luego, para usarlo, insértelo donde quiera que comience la respuesta:

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

Pego esto en mi user.clj para que esté disponible en todas las sesiones REPL.


16

"Las mejores formas de depurar el código Clojure, mientras se usa la respuesta"

Ligeramente a la izquierda, pero "usando el REPL por sí mismo".

Llevo más de un año escribiendo al aficionado Clojure y no he sentido una gran necesidad de herramientas de depuración. Si mantiene sus funciones pequeñas y ejecuta cada una con las entradas esperadas en el REPL y observa los resultados, entonces debería ser posible tener una imagen bastante clara de cómo se comporta su código.

Creo que un depurador es más útil para observar ESTADO en una aplicación en ejecución. Clojure hace que sea fácil (¡y divertido!) Escribir en un estilo funcional con estructuras de datos inmutables (sin cambios de estado). Esto reduce enormemente la necesidad de un depurador. Una vez que sé que todos los componentes se comportan como esperaba (prestando especial atención a los tipos de cosas), el comportamiento a gran escala rara vez es un problema.


Esto es principalmente cierto, pero cuando tiene recurrencia en múltiples funciones, por ejemplo, no es tan fácil.
John


9

Para IntelliJ hay un excelente complemento de Clojure llamado Cursive . Entre otras cosas, proporciona un REPL que puede ejecutar en modo de depuración y recorrer su código Clojure tal como lo haría, por ejemplo, Java.

Sin embargo, respaldaría la respuesta de Peter Westmacott porque, según mi experiencia, solo ejecutar partes de mi código en el REPL es la mayoría de las veces una forma suficiente de depuración.


Estaba usando La Clojure con éxito, pero parece estar muriendo a favor de la cursiva ahora github.com/JetBrains/la-clojure/blob/master/README.md
leeor

Pero cómo depurar Leiningen, muestra:Error running 'ring server': Trampoline must be enabled for debugging
Gank

Eso parece ser específico ringo lein, ¿quizás valga la pena publicar una pregunta por separado?
dskrvk

6

A partir de 2016, puede utilizar Debux , una biblioteca de depuración simple para Clojure / Script que funciona junto con su respuesta, así como la consola de su navegador. Puede rociar dbg(depurar) o clogmacros (console.log) en su código y observar fácilmente los resultados de funciones individuales, etc., impresas en su REPL y / o consola.

Del archivo Léame del proyecto :

Uso básico

Este es un ejemplo simple. La macro dbg imprime un formulario original e imprime el valor evaluado en la ventana REPL. Luego devuelve el valor sin interferir con la ejecución del código.

Si envuelve el código con dbg como este,

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

lo siguiente se imprimirá en la ventana REPL.

Salida REPL:

dbg: (+ 10 20) => 30

Dbg anidado

La macro dbg se puede anidar.

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

Salida REPL:

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

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


5

Hugo Duncan y sus colaboradores continúan haciendo un trabajo increíble con el proyecto ritz . Ritz-nrepl es un servidor nREPL con capacidades de depuración. Vea la charla de Hugo's Debuggers en Clojure en Clojure / Conj 2012 para verlo en acción, en el video algunas de las diapositivas no son legibles, por lo que es posible que desee ver las diapositivas desde aquí .




1

Aquí hay una buena macro para depurar letformas complicadas :

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

... y un ensayo que explica su uso .


-4

Versión funcional de def-let, que convierte un let en una serie de defs. Un poco de crédito va hasta aquí

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

Uso: necesita citar el contenido con una cita, p. Ej.

(def-let '[a 1 b 2 c (atom 0)])
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.