Подтвердить что ты не робот

Как получить лучшую обратную связь от ошибок Clojure?

Мне очень сложно отлаживать ошибки Clojure, которые у меня есть в моем коде, по сравнению со всеми другими языками программирования, которые я использовал. Мой основной язык программирования - Java, и я очень новичок в Clojure. Большая часть моего времени написания Clojure проводится, пытаясь выяснить: "Почему я получаю эту ошибку?" и я хотел бы изменить это. Я использую CounterClockWise как свою первую IDE. Я не знаю, как использовать Emacs (пока?).

Вот пример:

(ns cljsandbox.core)

(def l [1 2 3 1])

(defn foo
  [l]
  (->> l
    (group-by identity)
    ;vals  ;commented out to show my intent
    (map #(reduce + %))))

Здесь я ошибочно считал, что group-by возвращает список списков, но он фактически возвращает карту <key, list<value>> или, как бы вы ее не выражали в терминах Java. Появится сообщение об ошибке:

ClassCastException Clojure.lang.PersistentVector нельзя передать в java.lang.Number Clojure.lang.Numbers.add(Numbers.java:126)

Это не очень полезно, потому что нет трассировки стека. Если я набираю (e), он говорит:

java.lang.ClassCastException: clojure.lang.PersistentVector cannot be cast to java.lang.Number
 at clojure.lang.Numbers.add (Numbers.java:126)
    clojure.core$_PLUS_.invoke (core.clj:944)
    clojure.core.protocols/fn (protocols.clj:69)
    clojure.core.protocols$fn__5979$G__5974__5992.invoke (protocols.clj:13)
    clojure.core$reduce.invoke (core.clj:6175)
    cljsandbox.core$foo$fn__1599.invoke (core.clj:10)
    clojure.core$map$fn__4207.invoke (core.clj:2487)
    clojure.lang.LazySeq.sval (LazySeq.java:42)

Я понятия не имею, как я могу перейти от этого сообщения об ошибке к пониманию: "Вы считали, что проходили список списков в map, но вы действительно проходили в виде данных карты". Трассировка стека показывает, что проблема была обнаружена внутри reduce, а не внутри group-by, но IMO, это не то место, где я, как человек, сделал мою ошибку. То, что программа обнаружила ошибку, была сделана.

Проблемы, подобные этим, могут занять около 15 минут. Как я могу сделать это меньше времени?


Я слишком много знаю, чтобы ожидать, что динамический язык поймает эти ошибки. Но я чувствую, что сообщения об ошибках других динамических языков, таких как javascript, гораздо полезнее.

Я получаю отчаяние здесь, потому что я читал код Clojure в течение 1-2 месяцев, и мне кажется, что я должен лучше разбираться в этих проблемах. Я попытался использовать :pre/:post для функций, но у него есть некоторые проблемы

  • Отчеты о :pre/:post отстой. Он только печатает буквально то, что вы тестируете. Поэтому, если вы не приложите много усилий, сообщение об ошибке не поможет.
  • Это не очень идиоматично. Единственный код, который я видел, который использует :pre/:post, - это статьи, в которых объясняется, как использовать :pre/:post.
  • Это настоящая боль, чтобы вытащить шаги потокового макроса в их собственный defn, чтобы я мог поместить в них :pre/:post.
  • Если бы я придерживался этой практики религиозно, я думаю, что мой код может стать столь же многословным, как Java. Я буду изобретать систему типов вручную.

Я дошел до того места, где я перекачал свой код с помощью проверок безопасности:

(when (= next-url url)
            (throw (IllegalStateException. (str "The next url and the current url are the same " url))))      
(when-not (every? map? posts-list)
            (throw (IllegalStateException. "parsed-html->posts must return a list of {:post post :source-url source-url}")))

Которая только фиксирует эту первую маркерную точку.

Я чувствую себя как

  • У меня есть процесс разработки, который очень, очень неправильный, и я не знаю его
  • Там есть какой-то инструмент/библиотека отладки, который я не знаю о том, что все остальные
  • У всех остальных есть такие проблемы, и это Clojure грязный маленький секрет/Все остальные используются для динамических языков и ожидают, что пройдут те же усилия, что и я, чтобы разрешить ошибки.
  • CounterClockWise имеет некоторую ошибку, которая делает мою жизнь более сложной, чем она должна быть
  • Я должен писать намного больше модульных тестов для моего кода Clojure, чем для моего кода Java. Даже если я пишу код.
4b9b3361

Ответ 1

В этом конкретном случае обнаружить источник проблемы легко:

  • У нас есть функция, которая будет применяться к известному вектору элементов. Мы также ожидаем определенного результата.

  • Применение функции приводит к проблеме. Тогда загляните внутрь функции; он является конвейером ->>.

  • Самый простой способ диагностики проблемы - оставить некоторые из заключительных этапов конвейера, чтобы увидеть, будут ли промежуточные этапы преобразования такими, какие мы ожидаем.

Выполнение 3. особенно очевидно в REPL; один подход заключается в def входном и промежуточном результатах на временные Vars, другой - на использование *1, *2 и *3. (Если конвейер длинный или вычисления занимают много времени, я бы рекомендовал сделать временный def не реже одного раза в несколько шагов, иначе *n может быть достаточно.)

В других случаях вы делаете что-то немного другое, но в любом случае разбить работу на управляемые фрагменты, которые будут воспроизводиться с помощью REPL, это ключ. Разумеется, знакомство с библиотеками последовательностей и коллекций Clojure ускоряет процесс совсем немного; но затем играть с ними в контексте небольших фрагментов фактической задачи, над которой вы работаете, является одним из лучших способов узнать о них.

Ответ 2

Лучший способ понять исключения clojure на данный момент (пока, вероятно, у нас есть clojure в clojure), следует понимать, что clojure реализуется с использованием классов Java, интерфейсов и т.д. Поэтому всякий раз, когда вы получаете любое такое исключение пытается сопоставить классы/интерфейсы, упомянутые в исключении, с понятиями clojure.

Например: в вашем текущем исключении можно легко сделать вывод о том, что clojure.lang.PersistentVector пытались ввести cast в java.lang.Number в методе clojure.lang.Numbers.add. Из этой информации вы можете просмотреть свой код и интуитивно выяснить, где вы используете add i.e + в своем коде, а затем диагностировать эту проблему из-за того, что каким-то образом это + получает вектор как параметр вместо числа.

Ответ 3

Я нахожу, что макрос clojure.tools.logging/spy очень полезен для отладки. Он печатает завернутое выражение, а также его значение. Если настройка clojure.tools.logging - это не то, что вы хотите сделать прямо сейчас (для этого требуются обычные настройки ведения журнала Java), вы можете использовать это:

(defmacro spy
  [& body]
  `(let [x# [email protected]]
     (printf "=> %s = %s\n" (first '~body) x#)
     x#))

* имейте в виду, что приведенный выше код не печатает значения ленивого seq, если он не был реализован. Вы можете vec lazy seq, чтобы заставить его реализовать - не рекомендуется для бесконечных seqs.

К сожалению, я не нашел хороший способ использовать макрос шпиона в макросе потоковой передачи, но этого достаточно для большинства других случаев.

Ответ 4

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

Это не очень зрелый проект и не обновлялся в течение года, но он уже успевает улучшить сообщения об ошибках. Кроме того, он находится в списке возможных GSoC 2015, поэтому мы можем увидеть значительное улучшение в ближайшее время!