我知道我可以用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的情况下,我如何做到这一点?
发布于 2011-12-20 00:10:53
出于线程安全的考虑,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时,旧变量将被重用。)
发布于 2011-12-17 19:32:02
我个人对你在Common Lisp中所做的事情的翻译是:
(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);-)。
发布于 2011-12-17 08:09:10
尽管在Clojure中这样做的方法是不这样做: Clojure讨厌可变状态(它是可用的,但不鼓励在每一件小事上使用它)。相反,请注意模式:您实际上是在计算(cons 4 (cons 3 (cons 2 (cons 1 (cons 0 nil)))))。这看起来非常像reduce (如果你喜欢,也可以是fold )。所以,(reduce (fn [acc x] (cons x acc)) nil (range 5)),它产生了你想要的答案。
https://stackoverflow.com/questions/8540855
复制相似问题