有没有一种方法可以在Common Lisp中使用迭代并同时避免副作用?

时间:2019-06-22 02:36:47

标签: common-lisp ansi-common-lisp

我已经编写了两个版本的lisp函数。两者之间的主要区别在于,一种是通过递归完成的,另一种是通过迭代完成的。

这是递归版本(无副作用!):

(defun simple-check (counter list)
  "This function takes two arguments: 
  the number 0 and a list of atoms. 
  It returns the number of times the 
  atom 'a' appears in that list."
  (if (null list)
      counter
      (if (equal (car list) 'a)
          (simple-check (+ counter 1) (cdr list))
          (simple-check counter (cdr list)))))

以下是迭代版本(带有副作用):

(defun a-check (counter list)
  "This function takes two arguments: 
  the number 0 and a list of atoms. 
  It returns the number of times the 
  atom 'a' appears in that list."
  (dolist (item list)
    (if (equal item 'a)
        (setf counter (+ counter 1))
        (setf counter (+ counter 0))))
  counter)

据我所知,它们都起作用。但是我真的很想避免迭代版本的副作用。我想回答两个问题:

  1. 是否可以避免副作用并保持迭代?
  2. 假设对#1的回答是肯定的,那么这样做的最佳方法是什么?

4 个答案:

答案 0 :(得分:3)

为完整起见,请注意Common Lisp具有内置的COUNT

(count 'a list)

答案 1 :(得分:2)

在某些方面,副作用或没有副作用之间的差异有些模糊。采用以下loop版本(忽略loop也有更好的方法):

(loop :for x :in list
      :for counter := (if (eq x 'a) (1+ counter) counter)
      :finally (return counter))

counter 设置了,还是反弹?即,是否修改了现有变量(如在setf中),还是创建了新的变量绑定(如在递归中)?

do版本非常类似于递归版本:

(do ((list args (rest list))
     (counter 0 (+ counter (if (eq (first list) 'a) 1 0))))
    ((endp list) counter))

与上述相同。

现在是“显而易见的” loop版本:

(loop :for x :in list
      :count (eq x 'a))

甚至没有用于计数器的显式变量。有副作用吗?

在内部,当然会产生影响:创建环境,建立绑定,尤其是在进行尾部调用优化的情况下,甚至在每个步骤都已销毁/替换的递归版本中。

我认为只有副作用会影响超出某些定义范围的事物。当然,如果您还可以在内部定义的级别上避免显式设置事物,而是使用一些更具声明性的表达式,则事物看起来会更加优雅。

答案 2 :(得分:2)

您还可以与mapmapcar和朋友进行迭代。

https://lispcookbook.github.io/cl-cookbook/iteration.html

我还建议您看看remove-if[-not]以及其他reduceapply

(length (remove-if-not (lambda (x) (equal :a x)) '(:a :b :a)))  ;; 2

答案 3 :(得分:1)

将计数器传递给递归过程是启用尾递归定义的一种方法。对于迭代定义,这是不必要的。 正如其他人指出的那样,有几种语言构造可以很好地解决所述问题。

我认为您从更一般的意义上对此感兴趣,例如当您找不到 一种直接解决问题的语言功能。 通常,可以通过将突变保持私有状态来维护功能接口,如下所示:

(defun simple-check (list)                                                                    
  "return the number of times the symbol `a` appears in `list`"                                 
  (let ((times 0))                                                                            
    (dolist (elem list times)                                                                 
      (when (equal elem 'a)                                                                   
        (incf times)))))