我知道我可以在 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 的情况下做到这一点?
我个人对您在 Common Lisp 中所做的事情的翻译是 Clojurewise:
(into (list) (range 5))
这导致:
(4 3 2 1 0)
一点解释:
该函数into
将所有元素连接到一个集合中,这里是一个新列表,使用(list)
来自其他集合创建,这里是 range 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))
,这会产生您正在寻找的答案。
为了线程安全,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
其含义始终为. my-list
如果嵌套函数关闭my-list
并且循环继续到下一次迭代,则嵌套函数将始终看到my-list
创建嵌套函数时的值。局部变量总是可以用它的值替换,而你在递归调用后得到的变量在某种意义上是一个不同的变量。
(Clojure 编译器执行优化,因此这个“新变量”不需要额外的空间:当需要记住一个变量时,它的值被复制,而当recur
被调用时,旧变量被重用。)
为此,我将使用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]] (inc x)) ; 只是为了弄清楚发生了什么 (0 1 2 3 4)
setf
是状态突变。Clojure 对此有非常具体的意见,并在您需要时提供相应的工具。在上述情况下你没有。
这是我一直在寻找的模式:
(loop [result [] x 5]
(if (zero? x)
result
(recur (conj result x) (dec x))))
我在Stuart Halloway 和 Aaron Bedra的Programming Clojure (Second Edition) 中找到了答案。
(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)