Lisp Can';更改列表
例如,我有一个函数Lisp Can';更改列表,lisp,common-lisp,clisp,Lisp,Common Lisp,Clisp,例如,我有一个函数stack push,它将元素推送到堆栈上 (defun stack-push (stack element) (if (not (listp element)) (setf stack (cons element stack)) (dolist (current-el (reverse element)) (setf stack (cons current-el stack))))) 但是当我像(stack p
stack push
,它将元素推送到堆栈上
(defun stack-push (stack element)
(if (not (listp element))
(setf stack (cons element stack))
(dolist (current-el (reverse element))
(setf stack (cons current-el stack)))))
但是当我像
(stack push*some stack*”(abcde))那样调用它时,它不会影响*some stack*
。你能解释一下原因吗?setf
带有一个类似于(setf堆栈(cons元素堆栈))
的符号扩展为(setq堆栈(cons元素堆栈))
。这通常在创建函数时发生。您的功能如下:
(defun stack-push (stack element)
(if (not (listp element)
(setq stack (cons element stack))
(dolist (current-el (reverse element))
(setq stack (cons current-el stack)))))
注意,我在这里只扩展了setf
。defun
和dolist
都变成了非常可怕的扩展,使代码更不可读。系统完全展开表单,以便运行的代码没有宏
setq
更新绑定,因此当它更新stack
时,它会更新该绑定,而不是*某些堆栈*
。如果您执行(堆栈推送*some stack*“a”)
,则会发生以下情况:
您传递函数*一些堆栈*
和“a”
。它评估其论点李>
*某些堆栈*
的计算结果是地址A有一个cons
单元格,例如(“b”)
“a”
是驻留在地址B的文本
参数被绑定到新绑定<代码>堆栈
指向A,元素指向B
元素
指向B(不是(listp元素));=>t
,代码跟随其后的结果(setf堆栈(cons元素堆栈))
运行时之前更改为(setq堆栈(cons元素堆栈))
李>
(cons元素堆栈)
使用B和A作为参数调用cons
。返回地址为C的新单元格(“a”“b”)
setq
更新,以便绑定stack
指向Cstack push
返回最后一个计算值,该值是第二个参数Csetq的结果
*某些堆栈*
。绑定从未被引用或更新。只有stack
更新为指向的新值
由于这是一个函数,您可以使用文本调用您的函数,如果参数是要更新的变量,这通常不起作用
(stack-push '("b") "a") ; ==> ("a" "b")
有一个叫做push
的表单。它不是一个函数。您可以看到它的功能:
(macroexpand '(push "a" *some-stack*))
; ==> (setq *some-stack* (cons "a" *some-stack*))
因此,要使
push
工作,您需要第二个参数是setf
-able。使用文本进行尝试会扩展为无法命名的代码。对于类型,您可以制作自己的setf
扩展器,以便setf
和push
工作 setf
带有符号,如(setf堆栈(cons元素堆栈))
扩展为(setq堆栈(cons元素堆栈))
。这通常在创建函数时发生。您的功能如下:
(defun stack-push (stack element)
(if (not (listp element)
(setq stack (cons element stack))
(dolist (current-el (reverse element))
(setq stack (cons current-el stack)))))
注意,我在这里只扩展了setf
。defun
和dolist
都变成了非常可怕的扩展,使代码更不可读。系统完全展开表单,以便运行的代码没有宏
setq
更新绑定,因此当它更新stack
时,它会更新该绑定,而不是*某些堆栈*
。如果您执行(堆栈推送*some stack*“a”)
,则会发生以下情况:
*一些堆栈*
和“a”
。它评估其论点李>
*某些堆栈*
的计算结果是地址A有一个cons
单元格,例如(“b”)
“a”
是驻留在地址B的文本元素
指向B(不是(listp元素));=>t
,代码跟随其后的结果(setf堆栈(cons元素堆栈))
运行时之前更改为(setq堆栈(cons元素堆栈))
李>
(cons元素堆栈)
使用B和A作为参数调用cons
。返回地址为C的新单元格(“a”“b”)
setq
更新,以便绑定stack
指向Cstack push
返回最后一个计算值,该值是第二个参数Csetq的结果
*某些堆栈*
。绑定从未被引用或更新。只有stack
更新为指向的新值
由于这是一个函数,您可以使用文本调用您的函数,如果参数是要更新的变量,这通常不起作用
(stack-push '("b") "a") ; ==> ("a" "b")
有一个叫做push
的表单。它不是一个函数。您可以看到它的功能:
(macroexpand '(push "a" *some-stack*))
; ==> (setq *some-stack* (cons "a" *some-stack*))
因此,要使
push
工作,您需要第二个参数是setf
-able。使用文本进行尝试会扩展为无法命名的代码。对于类型,您可以制作自己的setf
扩展器,以便setf
和push
工作 请注意Sylvester的回答,这里有一个版本的堆栈推送
,乍一看是正确的,但实际上有一个丑陋的问题(感谢jkiiski指出了这一点!),然后是一个仍然存在问题的简单版本,最后是一个不存在问题的简单版本的变体
这是最初的版本。这与您的签名一致(它要么接受一个不能成为列表的参数,要么接受一个参数列表,并根据它看到的内容决定要做什么)
然而,我更倾向于使用&rest
参数编写它,如下所示。这个版本比较简单,因为它
? (macropp '(stack-push* (foo (a)) 1) 2)
-- (stack-push* (foo (a)) 1)
-> (setf (foo (a)) (append (reverse (list 1)) (foo (a))))
-> (let ((#:g86139 (a)))
(funcall #'(setf foo) (append (reverse (list 1)) (foo (a))) #:g86139))
(defun stackify (s &rest elements)
(append (reverse elements) s))
(define-modify-macro stack-push* (s &rest elements)
stackify)
? (macropp '(stack-push* (foo (a)) 1) 2)
-- (stack-push* (foo (a)) 1)
-> (let* ((#:g86170 (a)) (#:g86169 (stackify (foo #:g86170) 1)))
(funcall #'(setf foo) #:g86169 #:g86170))
-> (let* ((#:g86170 (a)) (#:g86169 (stackify (foo #:g86170) 1)))
(funcall #'(setf foo) #:g86169 #:g86170))
(defun macropp (form &optional (n 1))
(let ((*print-pretty* t))
(loop repeat n
for first = t then nil
for current = (macroexpand-1 form) then (macroexpand-1 current)
when first do (format t "~&-- ~S~%" form)
do (format t "~&-> ~S~%" current)))
(values))