Переопределение переменной let в цикле Clojure

StackOverflow https://stackoverflow.com/questions/940712

  •  06-09-2019
  •  | 
  •  

Вопрос

ХОРОШО.Я возился с 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>
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top