do与do*宏执行结果差异及Lisp循环相关问题咨询
Common Lisp中
do与do*宏的调试问题解析 1. do与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))))
执行输出:
CL-USER> (my-reverse '(10 20 30)) current: 10 current: 20 current: 30 (30 20 10)
替换为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))))
执行输出:
CL-USER> (my-reverse '(10 20 30)) current: 10 current: 20 current: 30 (NIL 30 20)
核心差异
do和do*的本质区别是循环变量的更新方式:
do采用并行更新:所有变量的步进表达式,都基于本轮循环开始时的变量值计算。当终止条件(null alist)为真时,直接返回acc,不会执行任何变量的步进操作,因此不会出现额外的nil。do*采用串行更新:变量按顺序更新,后面的变量步进会使用前面已经更新后的变量值。在最后一次循环中:- 执行循环体打印
30后,先将alist更新为(cdr '(30))即nil - 接着执行
acc的步进表达式(cons (car alist) acc),此时alist已经是nil,(car nil)返回nil,导致acc被追加了一个nil,最终结果变成(NIL 30 20)。
- 执行循环体打印
2. 提取(car alist)为变量x后的问题
无步进表达式的x版本
代码:
(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)))
输出:
CL-USER> (my-reverse '(10 20 30)) current: 10 current: 10 current: 10 (10 10 10)
原因
do*中如果变量没有指定步进表达式,其值会保持初始化时的固定值不变。这里x的初始值是(car '(10 20 30))即10,后续循环中没有更新,所以每次累积的都是10。
添加步进表达式后的版本
代码:
(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)))
输出:
CL-USER> (my-reverse '(10 20 30)) current: 10 current: 20 current: 30 (NIL 30 20)
原因
问题还是出在do*的串行更新逻辑:最后一次循环执行完打印30后,会先更新alist为nil,再更新x为(car nil)即nil,最后更新acc为(cons nil '(30 20)),导致结果多了一个nil。
3. 循环体中修改循环变量的错误
代码:
(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~%") )))
错误提示:illegal function call,指向(setf acc (cons x acc))
原因
这是语法错误,do/do*的循环体是一个或多个表达式的序列,不需要用额外的括号包裹。你把循环体写成((setf ...) (format ...)),相当于试图将(setf ...)的返回值当作函数调用,而setf的返回值不是函数,因此触发非法函数调用错误。
正确写法
循环体允许修改循环变量,只需去掉外层括号即可:
(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 t "current: ~a~%" x)))
执行后会得到正确结果(30 20 10)。
内容的提问来源于stack exchange,提问作者7stud
相关产品推荐
相关产品推荐

