Подтвердить что ты не робот

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

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

4b9b3361

Ответ 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>