2011-12-16 2 views
2

Я знаю, что могу сделать следующее в Common Lisp:SETF в Clojure

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?

+3

В Common Lisp это будет короче, так как (цикл для i downfrom 4 to 0 collect i). –

ответ

3

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 называется старая переменная повторно.)

+0

вы начинаете с i = 0, делите его на каждый шаг и останавливаетесь, когда i <5 - это означает сразу :) Вероятно, вы имеете в виду inc и i> 5. – skaurus

+0

@skaurus: Да, вы абсолютно правы! Исправлена. – raek

+1

вам, вероятно, еще нужно исправить < to > ... извините за это) – skaurus

1
 
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 имеет очень конкретные мнения по этому поводу и предоставляет инструменты для него , если вам это нужно. Вы не делаете этого.

7

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

8

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

;-).

0
(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) 
+0

Не очень много на clojure – BLUEPIXY

+0

Структура данных в clojure желательна быть неизменной, за исключением особых случаев. – BLUEPIXY

2

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

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

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

Смежные вопросы