Я только что начал играть с Clojure, и я записал маленький сценарий, чтобы помочь мне понять некоторые функции. Это начинается как это:
(def *exprs-to-test* [
"(filter #(< % 3) '(1 2 3 4 3 2 1))"
"(remove #(< % 3) '(1 2 3 4 3 2 1))"
"(distinct '(1 2 3 4 3 2 1))"
])
Затем это проходит *exprs-to-test*
, оценивает их всех и печатает вывод как это:
(doseq [exstr *exprs-to-test*]
(do
(println "===" (first (read-string exstr)) "=========================")
(println "Code: " exstr)
(println "Eval: " (eval (read-string exstr)))
)
)
Вышеупомянутый код все хорошо работает. Однако (read-string exstr)
повторяется так, я пытался использовать let
устранить повторение как так:
(doseq [exstr *exprs-to-test*]
(let [ex (read-string exstr)] (
(do
(println "===" (first ex) "=========================")
(println "Code: " exstr)
(println "Eval: " (eval ex))
)
))
)
Но это работает однажды на первый объект в *exprs-to-test*
, затем катастрофические отказы с a NullPointerException
. Почему имеет дополнение let
порождение катастрофического отказа?
У вас есть дополнительный набор скобок вокруг формы do
. Ваш код делает следующее:
((do ...))
Он пытается выполнить (как вызов функции) значение всей формы do
, но do
возвращает nil
, поскольку последний println
в форме do
возвращает nil
.
Обратите внимание, ваш стиль отступов нестандартный. Не следует ставить закрывающие скобки на отдельных строках. И let
имеет неявный do
, поэтому он вам не нужен. Попробуйте это:
user> (doseq [exstr *exprs-to-test*]
(let [ex (read-string exstr)]
(println "===" (first ex) "=========================")
(println "Code: " exstr)
(println "Eval: " (eval ex))))
=== filter =========================
Code: (filter #(< % 3) '(1 2 3 4 3 2 1))
Eval: (1 2 2 1)
=== remove =========================
Code: (remove #(< % 3) '(1 2 3 4 3 2 1))
Eval: (3 4 3)
=== distinct =========================
Code: (distinct '(1 2 3 4 3 2 1))
Eval: (1 2 3 4)
Брайан уже ответил на ваш вопрос, поэтому я просто хочу дать вам некоторые общие указания для let-form:
Я думаю, что другие ответы игнорируют слона в комнате: зачем вы это делаете? В вашем коде есть много вещей, которые заставляют меня беспокоиться, что вы идете по неправильному пути, изучая Clojure:
Лучший способ изучить API Clojure - использовать REPL. Вы должны настроить свою среду, будь то Vim, Emacs или IDE, чтобы вы могли легко перемещаться между статическим кодом в текстовых файлах и интерактивным REPL. Вот хорошая разбивка ряда IDE Clojure .
Теперь, что касается вашего кода, нужно помнить несколько вещей. Во-первых, почти никогда не бывает веских причин использовать eval. Если вы обнаружите, что делаете это, спросите себя, действительно ли это необходимо. Во-вторых, помните, что Clojure - это функциональный язык, и обычно вам не нужно использовать набор макросов «do». Макросы «do» полезны, когда вам нужно иметь побочные эффекты (в вашем примере побочным эффектом является println to * out *). Наконец, следует избегать использования глобальных переменных. Если вам действительно нужно использовать переменные, вам следует рассмотреть возможность использования макроса привязки для локальной привязки переменных к потоку с неизменяемыми значениями, чтобы не было проблем с параллелизмом.
Я определенно рекомендую вам найти время, чтобы изучить Programming Clojure или другой более подробный справочник по LISP, чтобы по-настоящему понять изменения, необходимые в вашем подходе к программированию для эффективного использования Clojure. Ваш небольшой образец здесь заставляет меня чувствовать, что вы пытаетесь написать империативный код на Clojure, который вообще не будет работать хорошо.