Отладка в Clojure?

Каковы наилучшие способы отладки кода Clojure при использовании repl?

Ответ 1

Там также есть точка, которая позволяет вам смотреть на входы и выходы выбранных функций.

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

выводит результат:

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

В Clojure 1.4, dotrace переместился:

Вам нужна зависимость:

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

И вам нужно добавить динамическое выражение ^: определение функции

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

Затем Боб снова твой дядя:

(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

У меня есть небольшой отладочный макрос, который мне очень полезен:

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

Вы можете вставить его туда, где хотите посмотреть, что происходит, и когда:

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

Ответ 3

Emacs CIDER получил исходный отладчик, который можно выставить в выражении внутри буфера Emacs и даже ввести новые значения. Вы можете прочитать все об этом здесь. Снимок экрана:

CIDER debug

Ответ 4

Мой любимый метод - это либеральное разбрызгивание println по всему коду... Простое и удобное включение и выключение благодаря макрос читателю #_ (что заставляет читателя читать следующую форму, затем притвориться, что она никогда не видела его). Или вы можете использовать макрос, расширяемый либо в переданном теле, либо в nil в зависимости от значения некоторой специальной переменной, например *debug*:

(defmacro debug-do [& body]
  (when *debug*
    `(do [email protected])))

При использовании (def *debug* false) он будет расширяться до nil. С true он будет расширяться до body, завернутого в do.


Принятый ответ на этот вопрос SO: Idiomatic Clojure для отчетов о ходе выполнения? очень полезно при отладке операций последовательности.


Тогда есть что-то, что в настоящее время несовместимо с swank- clojure REPL, но слишком хорошо, чтобы не упоминать: debug-repl. Вы можете использовать его в автономном REPL, который легко получить, например. с Leiningen (lein repl); и если вы запускаете свою программу из командной строки, то она собирается привести свой собственный REPL прямо в ваш терминал. Идея состоит в том, что вы можете отказаться от макроса debug-repl в любом месте, где бы вы ни находились, и вызвать его собственный REPL, когда выполнение программы достигает этой точки, со всеми локалями в области видимости и т.д. Несколько релевантных ссылок: Clojure debug-repl, Clojure трюки отладки, как "отладочный ответ" (в группе Clojure Google), debug-repl на Clojars.


swank- clojure выполняет адекватную работу по созданию встроенного отладчика SLIME при работе с кодом Clojure - обратите внимание на то, как неактивные биты stacktrace выделены серым цветом, поэтому легко найти актуальную проблему в код отлаживается. Следует иметь в виду, что анонимные функции без "тегов имен" отображаются в стеке, в основном, без какой-либо полезной информации; когда добавляется "тег имени", он появляется в стеке, и все снова хорошо:

(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

Вы также можете вставить код в REPL со всеми локальными привязками, используя Alex Osborne debug-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))))

Затем, чтобы использовать его, вставьте его туда, куда вы хотите, чтобы начать:

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

Я использую это в своем user.clj, чтобы он был доступен во всех сеансах REPL.

Ответ 6

"лучшие способы отладки кода Clojure при использовании repl"

Слегка левое поле, но "используя REPL itelf".

Я пишу hobbyist Clojure более года и не испытываю большой потребности в каких-либо инструментах для отладки. Если вы сохраняете свои функции небольшими и запускаете каждый из них с ожидаемыми входами в REPL и наблюдаете за результатами, тогда должно быть достаточно четкое представление о том, как работает ваш код.

Я считаю, что отладчик наиболее полезен для наблюдения за STATE в запущенном приложении. Clojure упрощает (и весело!) писать в функциональном стиле с неизменяемыми структурами данных (без изменения состояния). Это значительно уменьшает потребность в отладчике. Как только я знаю, что все компоненты ведут себя так, как я ожидаю (обращая особое внимание на типы вещей), тогда поведение большого масштаба редко бывает проблемой.

Ответ 7

Если вы используете emacs/slime/swank, попробуйте это в REPL:

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

(factorial 30)

Это не дает вам полную трассировку стека, как вы бы попали под LISP, но это хорошо для выталкивания вокруг.

Это прекрасная работа:

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

как было упомянуто в комментарии выше.

Ответ 8

Для IntelliJ есть отличный плагин Clojure, называемый Cursive. Помимо прочего, он предоставляет REPL, который вы можете запустить в режиме отладки и выполнить свой код Clojure так же, как и вы, например. Java.

Я бы ответил вторым Питером Уэстмакоттом, хотя в моем опыте просто запуск фрагментов моего кода в REPL в большинстве случаев является достаточной формой отладки.

Ответ 9

Hugo Duncan и соавторы продолжают делать удивительную работу с проектом ritz. Ritz-nrepl - это сервер nREPL с возможностями отладки. Посмотрите Hugo Отладчики в Clojure поговорите в Clojure/Conj 2012, чтобы увидеть его в действии, в видео некоторые слайды не читаются, поэтому вы можете захотеть для просмотра слайдов здесь.

Ответ 10

С 2016 года вы можете использовать Debux, простую библиотеку отладки для Clojure/Script, которая работает в сочетании с вашим а также консоль вашего браузера. Вы можете посыпать макросы dbg (debug) или clog (console.log) в свой код и легко наблюдать результаты отдельных функций и т.д., Печататься на REPL и/или консоли.

Из проекта Readme:

Основное использование

Это простой пример. Макрос dbg печатает оригинальную форму и pretty-prints вычисленное значение в окне REPL. Затем он возвращается значение, не мешая выполнению кода.

Если вы переносите код с помощью dbg, как это,

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

в REPL.

Выход REPL:

dbg: (+ 10 20) => 30

Вложенный dbg

Макрос dbg может быть вложен.

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

Выход REPL:

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

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

Ответ 11

Вот хороший макрос для отладки сложных форм let:

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

... и эссе, объясняющее его использование.

Ответ 12

Используйте spyscope, который реализует пользовательский макрос читателя, чтобы ваш код отладки был также производственным кодом https://github.com/dgrnbrg/spyscope

Ответ 14

Функциональная версия def-let, которая превращает let в ряд defs. Некоторые кредиты относятся к здесь

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

Использование: необходимо процитировать содержимое с помощью цитаты, например.

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