Переопределение переменной let'd в цикле Clojure - PullRequest
36 голосов
/ 02 июня 2009

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 !, но это дает "недопустимую цель назначения", так как я не в обязательной форме.

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

Ответы [ 4 ]

50 голосов
/ 02 июня 2009

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 оборачивает свое тело в явное для вас.) Если вы действительно, действительно хотели сделать это с vars ты должен был бы сделать что-то ужасное, как это.

(with-local-vars [x 128]
  (while (> (var-get x) 1)
    (println (var-get x))
    (var-set x (/ (var-get x) 2))))

Но это очень уродливо и совсем не идиоматично. Чтобы эффективно использовать Clojure, вы должны попытаться перестать думать в терминах изменчивого состояния. Это определенно сведет вас с ума, пытаясь написать код Clojure в не функциональном стиле. Через некоторое время вы можете найти приятным сюрпризом, как редко вам действительно нужны изменяемые переменные.

13 голосов
/ 18 июня 2009

Вары (это то, что вы получаете, когда вы что-то «определяете») не предназначены для переназначения (но могут быть):

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

Но я думаю, что это довольно однотипно.

6 голосов
/ 20 октября 2014

Если вы считаете, что наличие изменяемых локальных переменных в чистых функциях было бы хорошей удобной функцией, которая не причиняет вреда, поскольку функция все еще остается чистой, вас может заинтересовать это обсуждение в списке рассылки, где Рич Хики объясняет свои причины их удаления с языка. Почему не изменчивые местные жители?

Соответствующая часть:

Если локальные переменные являются переменными, то есть изменяемыми, тогда замыкания могут закрываться изменяемое состояние, и, учитывая, что замыкания могут сбежать (без каких-либо дополнительных запрет на то же самое), результат будет потокобезопасным. И люди будет, конечно, так, например, основанные на замыкании псевдообъекты. Результат было бы огромной дырой в подходе Clojure.

Без изменчивых местных жителей люди вынуждены использовать рекуррентный функционал циклическая конструкция. Поначалу это может показаться странным, но кратко, как петли с мутацией, и получающиеся в результате паттерны могут быть повторно использоваться в других местах в Clojure, то есть повторяться, уменьшать, изменять, коммутировать и т. д. все (логически) очень похожи. Хотя я мог обнаружить и чтобы избежать выхода мутационных пробок, я решил оставить это так для согласованности. Даже в самом маленьком контексте неизменяемые циклы легче понять и отладить, чем мутировать. В любом случае, Варс доступны для использования при необходимости.

Большинство последующих постов касаются реализации макроса with-local-vars;)

2 голосов
/ 05 июля 2016

Вы могли бы более идиоматически использовать iterate и take-while вместо

user> (->> 128
           (iterate #(/ % 2))
           (take-while (partial < 1)))

(128 64 32 16 8 4 2)
user>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...