如何检测Common Lisp函数无返回值?适配Berkeley Logo实现需求
问题描述
用户编写了以下Common Lisp函数:
(defun foo () () (values))
在REPL中执行时,输出被抑制,显示:
CL-USER> (foo) ; No value
用户需要检测这类函数是否真的无返回值(甚至不返回NIL),因为正在基于Common Lisp实现Berkeley Logo,需要区分两类操作:
- 仅作副作用的命令(如Logo中的
print) - 有返回值的操作(如Logo中的
add 2 3或2+3)
在Logo中,若用户直接输入有返回值的操作(如add 2 3),系统会提示:
I don't know what to do with 5.
要求用户补充为print add 2 3这类显式输出的形式。用户希望了解在Common Lisp中如何实现这种检测逻辑,曾考虑过包装解析后的Logo指令、利用条件系统,但不清楚具体实现方式。
解决方案
核心原理:检测返回值的数量
Common Lisp中,函数的返回值数量是明确的:
- 普通函数(即使没显式写返回值)默认返回1个值(最后一个表达式的结果,若为空则返回NIL)
- 用
(values)作为返回值时,函数会返回0个值,这正是Logo中“仅副作用命令”需要的特性
因此,检测逻辑的核心是:执行目标表达式后,判断其返回值的数量是否为0。若大于0,则说明是有返回值的操作,需要触发提示。
具体实现方法
1. 用multiple-value-list捕获返回值列表
multiple-value-list会将表达式的所有返回值打包成一个列表:
- 若表达式返回0个值,列表为空
- 若返回1个或多个值,列表包含对应元素
可以写一个工具函数来判断表达式是否无返回值:
(defun no-values-p (form) (zerop (length (multiple-value-list (eval form)))))
测试示例:
(no-values-p '(foo)) ; 返回T,因为foo返回0个值 (no-values-p '(+ 2 3)) ; 返回NIL,因为+返回1个值5 (no-values-p '(values 1 2)) ; 返回NIL,因为返回2个值
2. 在Logo执行流程中加入检测逻辑
当解析完用户输入的Logo指令,转换为Common Lisp表达式后,执行并检测返回值:
;; 假设parsed-form是解析后的CL表达式 (let* ((result-list (multiple-value-list (eval parsed-form)))) (unless (zerop (length result-list)) ;; 若有返回值,输出Logo风格的提示 (format t "I don't know what to do with ~a.~%" (car result-list))))
3. 设计Logo命令的返回值
将Logo中的命令(如print)设计为返回0个值,确保执行后不会触发提示:
(defun logo-print (value) (format t "~a~%" value) (values)) ; 显式返回0个值
这样,当用户输入print add 2 3时,解析后执行(logo-print (+ 2 3)),logo-print返回0个值,不会触发提示;而直接输入add 2 3会执行(+ 2 3),返回1个值5,触发提示。
注意事项
- 避免滥用
eval:如果Logo的解析结果是已编译的函数调用,直接调用即可,无需eval,比如(multiple-value-list (logo-add 2 3)) - 多返回值处理:若Logo操作可能返回多个值,可根据需求调整提示逻辑(比如提示所有返回值)
- 性能考虑:对于频繁执行的指令,
multiple-value-list的开销可以忽略,无需担心性能问题
内容的提问来源于stack exchange,提问作者Demihm Seinname
相关产品推荐
相关产品推荐

