сет в клоуре - PullRequest
       34

сет в клоуре

2 голосов
/ 17 декабря 2011

Я знаю, что могу сделать следующее в 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 ]

8 голосов
/ 17 декабря 2011

Мой личный перевод того, что вы делаете в 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)

; -).

7 голосов
/ 17 декабря 2011

Способ сделать это в Clojure - не делать этого: Clojure ненавидит изменяемое состояние (оно доступно, но использовать его для каждой мелочи не рекомендуется). Вместо этого обратите внимание на шаблон: вы действительно вычисляете (cons 4 (cons 3 (cons 2 (cons 1 (cons 0 nil))))). Это выглядит очень похоже на понижение (или фолд, если вы предпочитаете). Итак, (reduce (fn [acc x] (cons x acc)) nil (range 5)), что дает ответ, который вы искали.

3 голосов
/ 19 декабря 2011

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, старая переменная используется повторно.)

2 голосов
/ 17 декабря 2011

Для этого я бы использовал range с шагом, установленным вручную:

(range 4 (dec 0) -1) ; => (4 3 2 1 0)

dec уменьшает конечный шаг на 1, так что мы получаем значение 0.

1 голос
/ 22 декабря 2011

Это шаблон, который я искал:

(loop [result [] x 5]
  (if (zero? x)
    result
    (recur (conj result x) (dec x))))

Я нашел ответ в Программирование Clojure (Второе издание) Стюарта Халлоуэя и Аарона Бедра .

1 голос
/ 17 декабря 2011
user=> (range 5)
(0 1 2 3 4)
user=> (take 5 (iterate inc 0))
(0 1 2 3 4)
user=> (for [x [-1 0 1 2 3]]
         (inc x)) ; just to make it clear what's going on
(0 1 2 3 4)

setf - это мутация состояния.Clojure имеет очень конкретные мнения по этому поводу и предоставляет инструменты для этого , если вам это нужно .Вы не в вышеуказанном случае.

0 голосов
/ 17 декабря 2011
(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)
...