Сет в клоуре
Я знаю, что могу сделать следующее в Common Lisp:
CL-USER> (let ((my-list nil))
(dotimes (i 5)
(setf my-list (cons i my-list)))
my-list)
(4 3 2 1 0)
Как мне сделать это в Clojure? В частности, как мне это сделать, не имея setf в Clojure?
7 ответов
Clojure запрещает мутацию локальных переменных ради безопасности потока, но все еще возможно писать циклы даже без мутации. В каждом цикле цикла вы хотите my-list
иметь другое значение, но это также может быть достигнуто с помощью рекурсии:
(let [step (fn [i my-list]
(if (< i 5)
my-list
(recur (inc i) (cons i my-list))))]
(step 0 nil))
У Clojure также есть способ "просто выполнить цикл" без создания новой функции, а именно loop
, Это выглядит как let
, но вы также можете перейти к началу его тела, обновить привязки и снова запустить тело с recur
,
(loop [i 0
my-list nil]
(if (< i 5)
my-list
(recur (inc i) (cons i my-list))))
"Обновление" параметров с помощью рекурсивного хвостового вызова может выглядеть очень похоже на изменение переменной, но есть одно важное отличие: при вводе my-list
в вашем коде Clojure его значение всегда будет всегда my-list
, Если вложенная функция закрывается my-list
и цикл продолжается до следующей итерации, вложенная функция всегда будет видеть значение, которое my-list
имел, когда была создана вложенная функция. Локальная переменная всегда может быть заменена ее значением, а переменная, которую вы имеете после выполнения рекурсивного вызова, в некотором смысле является другой переменной.
(Компилятор Clojure выполняет оптимизацию, так что для этой "новой переменной" не требуется дополнительного места: когда переменная должна быть запомнена, ее значение копируется и когда recur
называется старая переменная используется повторно.)
Мой личный перевод того, что вы делаете в Common Lisp, будет Clojurewise:
(into (list) (range 5))
что приводит к:
(4 3 2 1 0)
Небольшое объяснение:
Функция into
объединяет все элементы в коллекцию, здесь новый список, созданный с (list)
из какой-то другой коллекции, здесь ассортимент 0 .. 4
, Поведение conj
отличается в зависимости от структуры данных. Для списка conj
ведет себя как cons
: он помещает элемент в начало списка и возвращает его как новый список. Так что же это такое:
(cons 4 (cons 3 (cons 2 (cons 1 (cons 0 (list))))))
что похоже на то, что вы делаете в Common Lisp. Разница в Clojure заключается в том, что мы постоянно возвращаем новые списки, а не изменяем один список. Мутация используется только тогда, когда это действительно необходимо в Clojure.
Конечно, вы также можете получить этот список сразу, но это, вероятно, не то, что вы хотели знать:
(range 4 -1 -1)
или же
(reverse (range 5))
или... самая короткая версия, которую я могу придумать:
'(4 3 2 1 0)
;-).
А вот способ сделать это в Clojure - не делать этого: Clojure ненавидит изменяемое состояние (оно доступно, но использовать его для каждой мелочи не рекомендуется). Вместо этого, обратите внимание на шаблон: вы действительно вычисляете (cons 4 (cons 3 (cons 2 (cons 1 (cons 0 nil)))))
, Это выглядит очень похоже на понижение (или фолд, если вы предпочитаете). Так, (reduce (fn [acc x] (cons x acc)) nil (range 5))
, который дает ответ, который вы искали.
Для этого я бы использовал range
с шагом, установленным вручную:
(range 4 (dec 0) -1) ; => (4 3 2 1 0)
dec
уменьшает конечный шаг на 1, так что мы получаем значение 0.
пользователь => (диапазон 5) (0 1 2 3 4) пользователь => (взять 5 (итерация вкл. 0)) (0 1 2 3 4) пользователь => (для [x [-1 0 1 2 3]] (вкл. х)); просто чтобы было понятно, что происходит (0 1 2 3 4)
setf
это государственная мутация. Clojure имеет очень конкретные мнения по этому поводу и предоставляет инструменты для этого, если вам это нужно. Вы не в вышеуказанном случае.
Это шаблон, который я искал:
(loop [result [] x 5]
(if (zero? x)
result
(recur (conj result x) (dec x))))
Я нашел ответ в Программировании Clojure (Второе издание) Стюарта Хэллоуэя и Аарона Бедры.
(let [my-list (atom ())]
(dotimes [i 5]
(reset! my-list (cons i @my-list)))
@my-list)
(def ^:dynamic my-list nil);need ^:dynamic in clojure 1.3
(binding [my-list ()]
(dotimes [i 5]
(set! my-list (cons i my-list)))
my-list)