setf in Clojure

时间:2011-12-16 22:26:38

标签: clojure lisp setf

我知道我可以在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中执行此操作?特别是,如果没有Clojure中的setf,我该怎么做呢?

7 个答案:

答案 0 :(得分:8)

我对您在Common Lisp中所做的事情的个人翻译将是Clojurewise:

(into (list) (range 5))

导致:

(4 3 2 1 0)

一点解释:

函数into将所有元素连接到集合,这里是一个新列表,使用(list)从其他集合创建,此处范围为0 .. 4conj的行为因数据结构而异。对于列表,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)

; - 。)

答案 1 :(得分:7)

在Clojure中做到这一点的方法是不要这样做:Clojure讨厌可变状态(它是可用的,但不鼓励使用它用于每一件小事)。相反,请注意模式:你真的在计算(cons 4 (cons 3 (cons 2 (cons 1 (cons 0 nil)))))。这看起来非常像减少(如果你愿意,还可以折叠)。所以,(reduce (fn [acc x] (cons x acc)) nil (range 5)),它产生了你正在寻找的答案。

答案 2 :(得分: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))))

使用递归尾调用“更新”参数看起来非常类似于变异变量,但有一个重要区别:当您在Clojure代码中键入my-list时,其含义始终值<{em> of my-list。如果嵌套函数关闭my-list并且循环继续到下一次迭代,则嵌套函数将始终看到创建嵌套函数时my-list所具有的值。局部变量总是可以用它的值替换,并且在进行递归调用之后你拥有的变量在某种意义上是一个不同的变量。

(Clojure编译器执行优化,因此这个“新变量”不需要额外的空间:当需要记住变量时,会复制其值,并且当调用recur时,将重用旧变量。 )

答案 3 :(得分:2)

为此,我将range与手动设置步骤一起使用:

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

dec减少1的结束步骤,以便我们得到0值。

答案 4 :(得分: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对此有非常具体的意见,并为其提供的工具,如果你需要它。在上述情况下你没有。

答案 5 :(得分:1)

这是我一直在寻找的模式:

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

我在Programming Clojure (Second Edition) by Stuart Halloway and Aaron Bedra找到了答案。

答案 6 :(得分: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)