Depurando no Clojure? [fechadas]


227

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


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

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)


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


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

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.


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.


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.


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


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 .




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 .


-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)])
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.