修改函数的参数

Mik*_*one 3 lisp common-lisp

(setf list (loop for i from 1 to 12 collect i))
(defun removef (item seq)
  (setf seq (remove item seq)))


CL-USER> (removef 2 list)
(1 3 4 5 6 7 8 9 10 11 12)

CL-USER> (removef 3 list)
(1 2 4 5 6 7 8 9 10 11 12)
Run Code Online (Sandbox Code Playgroud)

为什么不removef真正修改变量?

Dir*_*irk 7

在Common Lisp中,参数是"通过标识"传递的(这个术语可以追溯到D. Rettig,它是Allegro Common Lisp实现的开发者之一).想想通过值传递的指针(对堆对象),对于大多数Lisp对象(如字符串,向量,当然还有列表)都是如此;事情稍微复杂一些,因为实现也可能具有直接值,但是旁边这一点).

所述setfseq功能的修改的(私人的,词汇)变量绑定.这种变化在外面是不可见的removef.

为了removef能够在通话时影响周围环境,您需要将其设为宏:

(defmacro removef (element place)
   `(setf ,place (remove ,element ,place)))
Run Code Online (Sandbox Code Playgroud)

您可能希望了解一般参考setf的概念.请注意,我上面提供的宏版本不是它应该如何实际完成的!有关详细信息,请阅读及其丑陋的细节.removefget-setf-expansion

如果您只想破坏性地修改列表,请考虑使用delete而不是删除,但要注意,这可能会产生意想不到的后果:

(delete 2 '(1 2 3 4))
Run Code Online (Sandbox Code Playgroud)

ANSI标准不允许(您破坏性地修改文字对象,即代码的一部分).在这个例子中,错误很容易被发现,但是如果你在某个callstack中有7帧深,那么处理起源并不完全清楚的值,这就成了一个真正的问题.无论如何,甚至

(setf list (list 1 2 3 4))
(delete 1 list)
list
Run Code Online (Sandbox Code Playgroud)

尽管如此,起初可能会令人惊讶

(setf list (list 1 2 3 4))
(delete 2 list)
list
Run Code Online (Sandbox Code Playgroud)

似乎"有效".本质上,第一个例子不能按预期工作,因为该函数delete与原始版本具有相同的问题removef,即它不能改变调用者的list变量概念,所以即使对于破坏性版本,正确的方法是:

(setf list (delete 1 (list 1 2 3 4)))
Run Code Online (Sandbox Code Playgroud)