在lisp中,我可以将参数传递给函数并在函数内进行更改。 (AKA破坏性功能)。但是,在Clojure中,我已经读过某个地方,在同一个函数中改变给定的参数是不允许的。例如:
(defn add-two-lists [list1 list2]
(for [n (range (count list1))]
(+ (nth list1 n) (nth list2 n))))
这是一个普通函数,它的输出是两个相同列表的相加。但是,我想要这样的事情:
(defn add-two-lists [list1 list2 added_list]
(set! added_list
(for [n (range (count list1))]
(+ (nth list1 n) (nth list2 n)))))
也许我对set!
的使用是错误的或误用的,我仍然会遇到错误。有没有一种优雅的方法来破坏性地修改Clojure中的参数?
答案 0 :(得分:12)
在Clojure中不鼓励进行破坏性修改 - 我鼓励您在不诉诸破坏性更新的情况下找到编写代码的方法。
本着给出Clojurey解决方案的精神,我会按如下方式编写add-two-lists
函数:
(defn add-two-lists [list1 list2]
(map + list1 list2))
这有一些优点:
nth
本身就是列表上的O(n)操作。答案 1 :(得分:6)
Clojure提供了几种在这种情况下运行良好的可变类型,例如你可以pass an atom
到函数并让它在该原子中设置值。
(defn add-two-lists [list1 list2 added_list]
(reset! added_list
(for [n (range (count list1))]
(+ (nth list1 n) (nth list2 n)))))
然后在你调用它之后,用@
/ deref
从原子中获取值
编辑:如果效率是目标,那么使用transient collection可能有帮助
答案 2 :(得分:2)
with-local-vars宏允许您创建可以使用var-set修改的线程本地绑定变量。您还必须使用var-get访问var的值,该值可以缩短为@。
(defn add-two-lists [list1 list2 added-list]
(var-set added-list
(for [n (range (count list1))]
(+ (nth list1 n) (nth list2 n)))))
(with-local-vars [my-list nil]
(add-two-lists '(1 2 3) '(3 4 5) my-list)
@my-list)
修改强> 的
在样式注释中,您可以使用map添加两个列表,而无需使用第n个函数随机访问每个列表中的每个索引:
(defn add-two-lists [list1 list2 added-list]
(var-set added-list (map + list1 list2)))
答案 3 :(得分:1)
来自set!
上的clojure文档 Note - you cannot assign to function params or local bindings. Only Java fields, Vars, Refs and Agents are mutable in Clojure.
通常在选择功能语言的课程中,建议您不要使用for循环和分配。相反,你应该赞成递归和函数组合。
因此,如果我想在列表的每个元素中添加2,在命令式语言中,我只会执行for循环,但在函数式语言中,我会使用递归
user=> (def add2
(fn [mylist]
(if
(empty? mylist)
nil
(cons (+ (first mylist) 2) (add2 (rest mylist))))))
user=> (add2 (list 1 2 3))
(3 4 5)