Depurando no Clojure? [fechadas]

227

Quais são as melhores maneiras de depurar o código Clojure, enquanto usa o repl?

Arun R
fonte
Em adições às respostas abaixo, veja 'ferramentas e técnicas de depuração' no guia de REPL: clojure.org/guides/repl/...
Valentin Waeselynck

Respostas:

158

Há também o dotrace, que permite observar as entradas e saídas das funções selecionadas.

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

produz a saída:

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

No Clojure 1.4, dotracemudou:

Você precisa da dependência:

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

E você precisa adicionar a dinâmica ^: à definição da função

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

Então Bob é mais uma vez seu tio:

(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
fonte
2
Legal, mas como você consegue o clojure para encontrar 'clojure.contrib.trace? Eu tenho o jar clojure-contrib no meu caminho de classe, mas o REPL dizuser=> (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
Você poderia estar digitando errado o clojure como fechamento ou isso é um erro de digitação no comentário? Você pode carregar outras bibliotecas clojure.contrib?
John Lawrence Aspden
12
A partir da versão 1.3, foi movido para clojure.tools.trace ( github.com/clojure/tools.trace )
George
4
Se você estiver obtendo: "IllegalStateException não pode vincular dinamicamente var não dinâmico", consulte aqui: stackoverflow.com/questions/8875353/…
Cornelius
2
Também está funcionando na versão 1.5? Estou aprendendo Clojure com os koans clojure, mas ainda não consigo fazer o dotrace funcionar.
Nha
100

Eu tenho uma pequena macro de depuração que acho muito útil:

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

Você pode inseri-lo onde quiser assistir o que está acontecendo 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)
John Lawrence Aspden
fonte
Muito parecido clojure.tools.trace/trace.
Zaz 15/05
4
Ainda melhor: Spyscope .
Zaz
@Zaz Eu concordo totalmente. Spyscope é incrível! Ainda melhor talvez do que um depurador. Certamente para digitar.
J Atkin
66

O CIDER do Emacs possui um depurador de origem que pode ser expresso por expressão dentro de um buffer Emacs e até injetar novos valores. Você pode ler tudo sobre isso aqui . Uma captura de tela demo:

Depuração do CIDER

Amumu
fonte
46

Meu método favorito é uma aspersão liberal de printlns por todo o código ... É fácil ativá-los e desativá-los graças à #_macro do leitor (que faz com que o leitor leia da seguinte forma, depois finja que nunca o viu). Ou você pode usar uma macro expandindo para um corpo passado ou nildependendo do valor de alguma variável especial, digamos *debug*:

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

Com um (def *debug* false), isso será expandido para nil. Com true, ele será expandido para bodyenvolto em um do.


A resposta aceita para esta pergunta do SO: Cloneure Idiomatic para relatórios de progresso? é muito útil ao depurar operações de sequência.


Então há algo que é atualmente incompatível com Swank-clojure 's REPL, mas é bom demais para não mencionar: debug-repl. Você pode usá-lo em um REPL independente, o que é fácil de obter, por exemplo, com Leiningen ( lein repl); e se você estiver iniciando seu programa a partir da linha de comando, ele trará o seu próprio REPL diretamente no seu terminal. A idéia é que você pode soltar a debug-replmacro em qualquer lugar que desejar e exibir a sua própria REPL quando a execução do programa atingir esse ponto, com todos os locais no escopo etc. Alguns links relevantes: O Clojure debug-repl , o Clojure debug -repl truques , que tal um depurador-repl (no grupo Clojure Google), depurador-repl em Clojars .


O swank-clojure faz um trabalho adequado para tornar o depurador interno do SLIME útil ao trabalhar com o código Clojure - observe como os bits irrelevantes do stacktrace ficam acinzentados, para que seja fácil encontrar o problema real no código que está sendo depurado. Um aspecto a ter em mente é que funções anônimas sem "tags de nome" aparecem no rastreamento de pilha com basicamente nenhuma informação útil anexada a elas; Quando uma "tag de nome" é adicionada, ela aparece no rastreamento de pilha e tudo está bem novamente:

(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
fonte
5
Na verdade, há uma versão do debug-repl que funciona com Swank agora: hugoduncan.org/post/2010/... (Alerta de spoiler: é incrível)
1
Certo, e é bom ter um link aqui, obrigado! Concordou em incrível. :-)
Michał Marczyk
Se esse é o seu estilo, você pode gostar da biblioteca de debux mencionada em uma resposta subsequente. github.com/philoskim/debux
Mallory-Erik
@ Mallory-Erik Obrigado, vou dar uma olhada!
Michał Marczyk
37

Você também pode inserir código para se inserir em um REPL com todas as ligações locais, usando o de 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))))

Em seguida, para usá-lo, insira-o onde quiser que a repl seja iniciada:

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

Eu colo isso no meu user.clj para que fique disponível em todas as sessões do REPL.

thnetos
fonte
16

"melhores maneiras de depurar o código Clojure, enquanto usa o repl"

Campo ligeiramente esquerdo, mas 'usando o próprio REPL'.

Escrevo o hobby de Clojure há mais de um ano e não sinto grande necessidade de ferramentas de depuração. Se você mantiver suas funções pequenas e executar cada uma delas com as entradas esperadas no REPL e observar os resultados, será possível ter uma imagem bastante clara de como seu código está se comportando.

Acho que um depurador é mais útil para observar STATE em um aplicativo em execução. O Clojure torna fácil (e divertido!) Escrever em um estilo funcional com estruturas de dados imutáveis ​​(sem alteração de estado). Isso reduz enormemente a necessidade de um depurador. Quando eu sei que todos os componentes se comportam como eu esperava (prestando atenção especial aos tipos de coisas), o comportamento em larga escala raramente é um problema.

Peter Westmacott
fonte
Isso é verdade principalmente, mas quando você tem recursão em várias funções, por exemplo, não é tão fácil.
John John
9

Para o IntelliJ, existe um excelente plugin Clojure chamado Cursive . Entre outras coisas, ele fornece um REPL que você pode executar no modo de depuração e percorrer seu código Clojure como faria para, por exemplo, Java.

Eu preferiria a resposta de Peter Westmacott, embora na minha experiência apenas executar partes do meu código no REPL seja, na maioria das vezes, uma forma suficiente de depuração.

dskrvk
fonte
Eu estava usando o La Clojure com sucesso, mas parece estar morrendo em favor do cursivo agora github.com/JetBrains/la-clojure/blob/master/README.md
leeor
Mas como depurar Leiningen, mostra:Error running 'ring server': Trampoline must be enabled for debugging
Gank
Isso parece específico ringou leintalvez valha a pena postar uma pergunta separada?
achou
6

A partir de 2016, você poderá usar o Debux , uma biblioteca de depuração simples para Clojure / Script que funciona em conjunto com seu repl e com o console do navegador. Você pode polvilhar dbg(depurar) ou clog(console.log) macros no seu código e observar facilmente os resultados de funções individuais, etc., impressas no seu REPL e / ou console.

No Leiame do projeto :

Uso básico

Este é um exemplo simples. A macro dbg imprime um formulário original e imprime o valor avaliado na janela REPL. Em seguida, ele retorna o valor sem interferir na execução do código.

Se você quebrar o código com dbg assim,

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

o seguinte será impresso na janela REPL.

Saída REPL:

dbg: (+ 10 20) => 30

Dbg aninhado

A macro dbg pode ser aninhada.

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

Saída REPL:

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

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

Mallory-Erik
fonte
5

Hugo Duncan e colaboradores continuam fazendo um trabalho incrível com o projeto ritz . O Ritz-nrepl é um servidor nREPL com recursos de depuração. Assista a Debuggers de Hugo em Clojure conversando no Clojure / Conj 2012 para vê-lo em ação. No vídeo, alguns dos slides não são legíveis, portanto, você pode querer vê-los daqui .

Rodrigo Taboada
fonte
1

Vindo de Java e familiarizado com o Eclipse, gosto do que Counterclockwise (o plug-in do Eclipse para desenvolvimento do Clojure) tem a oferecer: http://doc.ccw-ide.org/documentation.html#_debug_clojure_code

yotsov
fonte
sem suporte adequado ao ponto de interrupção. Thread é difícil de matar.
CodeFarmer
1

Aqui está uma boa macro para depurar letformulários complicados :

(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 um ensaio explicando seu uso .

Jouni K. Seppänen
fonte
-4

Versão da função def-let, que transforma um let em uma série de defs. Algum crédito vai para aqui

(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: precisa citar o conteúdo com uma cotação, por exemplo

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