我正在摆弄
do macro
,我决定编写一个函数来反转列表:
(defun my-reverse (my-list)
(do ((alist my-list (cdr alist))
(acc nil (cons (car alist) acc)))
((null alist) acc)
(format t "current: ~a~%" (car alist))))
--output:--
CL-USER> (my-reverse '(10 20 30))
current: 10
current: 20
current: 30
(30 20 10)
到目前为止一切都很好。但我发现将
do
更改为 do*
会产生不同的结果:
(defun my-reverse (my-list)
(do* ((alist my-list (cdr alist))
(acc nil (cons (car alist) acc)))
((null alist) acc)
(format t "current: ~a~%" (car alist))))
--output:--
(CL-USER> (my-reverse '(10 20 30))
current: 10
current: 20
current: 30
(NIL 30 20)
对此有何解释?
另外,因为
(car alist)
在代码中出现了两次,我想我应该尝试初始化一个变量并将其设置为等于(car alist)
以简化代码:
(defun my-reverse (my-list)
(do* ((alist my-list (cdr alist))
(x (car alist))
(acc nil (cons x acc)))
((null alist) acc)
(format t "current: ~a~%" x)))
--output:--
CL-USER> (my-reverse '(10 20 30))
current: 10
current: 10
current: 10
(10 10 10)
好的,
x
需要一个步进器:
(defun my-reverse (my-list)
(do* ((alist my-list (cdr alist))
(x (car alist) (car alist))
(acc nil (cons x acc)))
((null alist) acc)
(format t "current: ~a~%" x)))
--output:--
CL-USER> (my-reverse '(10 20 30))
current: 10
current: 20
current: 30
(NIL 30 20)
那也没用。
接下来,我尝试在体内做积累:
(defun my-reverse (my-list)
(do* ((alist my-list (cdr alist))
(x (car alist) (car alist))
(acc nil))
((null alist) acc)
((setf acc (cons x acc))
(format "current: ~a~%")
))
但是,我收到了“非法函数调用”错误:
(setf acc (cons x acc))
主体是否允许更改循环变量?
do
和do*
之间的关系类似于let
和let*
之间的关系:并行分配与顺序分配。
引自超规格:
对于
,在更新任何 var 之前都会评估所有步骤形式;为 vars 赋值是并行完成的,就像通过do
一样。 因为所有步骤形式在任何变量更改之前都会被评估,所以评估时的步骤形式始终可以访问所有变量的旧值,即使其他步骤形式在它之前。psetq
因此,在
(cons (car alist) acc)
中,alist
的值是 前一个,而不是本次迭代设置的值。而 do*
,
对于
,计算第一个步进形式,然后将值分配给第一个 var,然后计算第二个步进形式,然后将值分配给第二个 var,依此类推;为变量赋值是按顺序完成的,就像通过do*
。setq
所以它看到了
alist
的当前版本,在第二次迭代中是 '(20 30)
,在最后一次迭代中是 nil
,而 (car nil)
是 nil
。