Переопределение переменной let в цикле Clojure
Вопрос
ХОРОШО.Я возился с Clojure и постоянно сталкиваюсь с одной и той же проблемой.Давайте возьмем этот небольшой фрагмент кода:
(let [x 128]
(while (> x 1)
(do
(println x)
(def x (/ x 2)))))
Теперь я ожидаю, что это распечатает последовательность, начинающуюся со 128:
128
64
32
16
8
4
2
Вместо этого это бесконечный цикл, печатающий 128 снова и снова.Очевидно, мой предполагаемый побочный эффект не работает.
Так как же мне переопределить значение x в таком цикле?Я понимаю, что это может быть не похоже на Лисп (возможно, я мог бы использовать анонимную функцию, которая рекурсивно обращается к самой себе), но если я не пойму, как установить такую переменную, я сойду с ума.
Другое мое предположение - использовать set!, но это дает «Неверная цель назначения», поскольку я не в форме привязки.
Пожалуйста, просветите меня, как это должно работать.
Решение
def
определяет переменную верхнего уровня, даже если вы используете ее в функции или внутреннем цикле какого-либо кода.Что вы получаете let
не являются варами.Пер документация для 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)))
Если вы Действительно хотел использовать изменяемое состояние, атом может работать.
(let [x (atom 128)]
(while (> @x 1)
(println @x)
(swap! x #(/ %1 2))))
(Вам не нужен do
; while
обертывает свое тело в явный для вас тип.) Если вы действительно очень хотел сделать это с вары вам придется сделать что-то ужасное, подобное этому.
(with-local-vars [x 128]
(while (> (var-get x) 1)
(println (var-get x))
(var-set x (/ (var-get x) 2))))
Но это очень некрасиво и вовсе не является идиоматическим Clojure.Чтобы эффективно использовать Clojure, вам следует перестать думать в терминах изменяемого состояния.Это определенно сведет вас с ума, если вы попытаетесь написать код Clojure в нефункциональном стиле.Через некоторое время вы можете оказаться приятным сюрпризом, насколько редко вам на самом деле нужны изменяемые переменные.
Другие советы
Вары (это то, что вы получаете, когда что-то «определяете») не предназначены для переназначения (но могут быть):
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
Но я думаю, что это довольно унидиоматично.
Если вы считаете, что наличие изменяемых локальных переменных в чистых функциях было бы хорошей и удобной функцией, которая не причиняет вреда, поскольку функция по-прежнему остается чистой, вас может заинтересовать это обсуждение в списке рассылки, где Рич Хики объясняет причины, по которым он удалил их из языка. . Почему не изменчивые местные жители?
Соответствующая часть:
Если бы локальные переменные были переменными, т.е.Мужественные, затем закрытия могут закрываться над изменчивым состоянием, и, учитывая, что закрытие может избежать (без какого-либо дополнительного запрета на то же самое), результатом будет ниточный Unsafe.И люди наверняка сделают это, напримерпсевдообъекты на основе замыканий.Результатом станет огромная дыра в подходе Клоджа.
Без изменчивых местных жителей люди вынуждены использовать Recur, функциональную конструкцию.Хотя сначала это может показаться странным, это так же лаконично, как и петли с мутацией, и полученные модели могут быть повторно использованы в другом месте в Clojure, т.е.Recur, уменьшить, альтер, поездка на работу и т. Д. Все (логически) очень похожи.Несмотря на то, что я мог обнаружить и предотвратить сбежать мутирующих закрытий, я решил сохранить это для последовательности.Даже в самых маленьких контекстах не обезжиривающие петли легче понять и отлаживать, чем мутировать.В любом случае, VAR доступны для использования при необходимости.
Большинство последующих постов посвящено реализации with-local-vars
макрос ;)
Вы могли бы более идиоматически использовать iterate
и take-while
вместо,
user> (->> 128
(iterate #(/ % 2))
(take-while (partial < 1)))
(128 64 32 16 8 4 2)
user>