Сет в клоуре

Я знаю, что могу сделать следующее в 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)
Другие вопросы по тегам