Переопределение переменной let'd в цикле Clojure
OK. Я возился с Clojure, и я постоянно сталкиваюсь с той же проблемой. Возьмем этот маленький фрагмент кода:
(let [x 128]
(while (> x 1)
(do
(println x)
(def x (/ x 2)))))
Теперь я ожидаю, что это напечатает последовательность, начинающуюся с 128 следующим образом:
128
64
32
16
8
4
2
Вместо этого это бесконечный цикл, который печатает 128 снова и снова. Очевидно, что мой предполагаемый побочный эффект не работает.
Итак, как мне переопределить значение x в цикле? Я понимаю, что это может быть не как Lisp вроде (я мог бы использовать анонимную функцию, которая, возможно, повторяет его сам), но если я не буду выяснять, как установить такую переменную, я сойду с ума.
Мое другое предположение было бы использовать set!, но это дает "Недопустимый целевой объект назначения", так как я не в форме привязки.
Пожалуйста, просветите меня, как это должно работать.
Ответы
Ответ 1
def
определяет toplevel var, даже если вы используете его в функции или внутреннем цикле некоторого кода. То, что вы получаете в let
, не является vars. Per документация для let
:
Локали, созданные с let, не являются переменными. После создания их значения никогда не меняются!
(Акцент не мой.) Вам не нужно изменять состояние для вашего примера здесь; вы можете использовать loop
и recur
.
(loop [x 128]
(when (> x 1)
(println x)
(recur (/ x 2))))
Если вы хотите быть фантазией, вы можете полностью исключить явный loop
.
(let [xs (take-while #(> % 1) (iterate #(/ % 2) 128))]
(doseq [x xs] (println x)))
Если вы действительно хотите использовать изменяемое состояние, может работать atom.
(let [x (atom 128)]
(while (> @x 1)
(println @x)
(swap! x #(/ %1 2))))
(Вам не нужно do
; while
обертывает свое тело в явном для вас). Если вы действительно хотели сделать это с помощью vars вам нужно сделать что-то ужасное, как это.
(with-local-vars [x 128]
(while (> (var-get x) 1)
(println (var-get x))
(var-set x (/ (var-get x) 2))))
Но это очень уродливо, и это вовсе не идиоматично Clojure. Чтобы эффективно использовать Clojure, вы должны попытаться перестать думать с точки зрения изменчивого состояния. Это определенно заставит вас сходить с ума, пытаясь написать код Clojure в нефункциональном стиле. Через некоторое время вы обнаружите, что приятно удивить, как редко вам нужны переменные переменные.
Ответ 2
Vars (то, что вы получаете, когда вы "определяете" что-то) не предназначено для переназначения (но может быть):
user=> (def k 1)
#'user/k
user=> k
1
Вам ничего не мешает:
user=> (def k 2)
#'user/k
user=> k
2
Если вы хотите локально устанавливаемое "место" для потока, вы можете использовать "привязку" и "установить!":
user=> (def j) ; this var is still unbound (no value)
#'user/j
user=> j
java.lang.IllegalStateException: Var user/j is unbound. (NO_SOURCE_FILE:0)
user=> (binding [j 0] j)
0
Итак, вы можете написать такой цикл:
user=> (binding [j 0]
(while (< j 10)
(println j)
(set! j (inc j))))
0
1
2
3
4
5
6
7
8
9
nil
Но я думаю, что это довольно унииоматично.
Ответ 3
Если вы считаете, что наличие изменяемых локальных переменных в чистых функциях будет приятной удобной функцией, которая не наносит вреда, потому что функция по-прежнему остается чистой, вам может быть интересно обсудить эту рассылку, в которой Rich Hickey объясняет причины его устранения от языка. Почему не изменяемые локали?
Релевантная часть:
Если locals были переменными, то есть изменчивыми, тогда замыкания могли бы закрыться изменяемое состояние, и, учитывая, что замыкания могут уйти (без каких-либо дополнительных запрет на то же), результат будет небезопасным. И люди безусловно, сделают это, например. основанные на закрытии псевдообъекты. Результат была бы огромной дырой в подходе Clojure.
Без изменчивых локальных жителей люди вынуждены использовать recur, функциональный цикл. Хотя сначала это может показаться странным, это так же кратко, как петли с мутацией, и результирующие узоры могут быть повторно используется в другом месте в Clojure, то есть повторять, уменьшать, изменять, коммутировать и т.д. все (логически) очень похожи. Хотя я мог обнаружить и предотвратить мутирование закрытия от побега, я решил сохранить его таким образом для согласованности. Даже в наименьшем контексте, не-мутирующие петли легче понять и отлаживать, чем мутировать. В любом случае, Vars доступны для использования, когда это необходимо.
Большинство последующих сообщений касается реализации макроса with-local-vars
;)
Ответ 4
Вы могли бы более идиоматически использовать iterate
и take-while
вместо этого,
user> (->> 128
(iterate #(/ % 2))
(take-while (partial < 1)))
(128 64 32 16 8 4 2)
user>