Что случилось с ленивой оценкой Clojure

Я скручиваю свою старую голову java/python способом clojure. Пожалуйста, помогите мне понять ленивую особенность clojure.

=> (def myvar (lazy-seq [1 2 (prn "abc")]))
#'user/myvar

Вышесказанное легко понять. Поскольку это ленивая последовательность, (prn "abc") не будет оцениваться, поэтому ничего не печатается.

=> (def myvar (lazy-seq [1 2 (prn undefined-var)]))
CompilerException java.lang.RuntimeException: Unable to resolve symbol: undefined-var in this context, compiling:(NO_SOURCE_PATH:1) 

Выше будет возникать ошибка, как вы можете видеть. Зачем?

Мое (неправильное) понимание состоит в том, что, поскольку это лениво, (prn undefined-var) может быть здесь юридически, даже "undefined-var" еще не был определен.

Пожалуйста, кто-нибудь направить мое понимание на правильный путь.

3 ответа

Решение

Оба приведенных выше ответа дают вам хорошую информацию по этому вопросу, но я постараюсь определить ключевую проблему здесь. Когда вы пишете s-выражение на REPL, как (+ x 2) происходят две вещи:

  1. Разбор читателем символов, которые дают формы, такие как символы,
  2. оценка форм.

Ленивая оценка откладывает второй шаг, но на первом этапе, когда читатель встречает undefined-var он пытается преобразовать его в символ и обнаруживает, что такой символ не был определен.

Когда читатель clojure находит

 (def myvar (lazy-seq [1 2 (prn undefined-var)]))

Он должен скомпилировать его, вот почему он выдает ошибку, поскольку undefined-var не определен. В первом случае он компилируется нормально, но не выполняется, пока вы не используете seq.

Похоже, вы понимаете правильно, просто вот как работает функция lazy-seq, это типичный пример:

user> (lazy-seq (cons 4 (range 5)))
(4 0 1 2 3 4)

lazy-seq почти всегда принимает выражение "против", где первый аргумент является первым элементом в последовательности, а второй аргумент является кодом для создания остальной части списка. Людям очень редко приходится использовать Lazy-Seq непосредственно в повседневной Clojure, используя такие формы, как mapreduce, filterи т. д. гораздо чаще встречаются.

код в функции для создания остальной части последовательности также необходим для возможности компиляции, в вашем случае вы также можете отложить компиляцию, используя eval хотя я бы рекомендовал не использовать eval для таких вещей в коде, который нужно читать другим; делает для отличного обучения, хотя;-)

user> (def myvar (lazy-seq (cons 1 (eval '(prn undefined-var))))) ; don't do this at work
#'user/myvar
user> myvar
; Evaluation aborted.
user> 
Другие вопросы по тегам