有没有办法改变Clojure中的参数?

时间:2012-08-01 01:30:35

标签: clojure

在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中的参数?

4 个答案:

答案 0 :(得分:12)

在Clojure中不鼓励进行破坏性修改 - 我鼓励您在不诉诸破坏性更新的情况下找到编写代码的方法。

本着给出Clojurey解决方案的精神,我会按如下方式编写add-two-lists函数:

(defn add-two-lists [list1 list2]
  (map + list1 list2))

这有一些优点:

  • 这是纯粹的功能
  • 它很懒,所以你甚至可以添加无限长度的列表(尝试使用破坏性更新的参数!)
  • 它的性能是O(n)是最优的 - 问题中的版本实际上是O(n ^ 2),因为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)