Common Lisp中and类型说明符未正常短路问题原因及解决方案问询
行为产生原因
Common Lisp 实现(此处为SBCL)会对复合类型说明符执行内部规范化展开,list类型在SBCL中等价于(or cons null),因此你写的(and list (satisfies check-len))会被重写为(or (and cons (satisfies check-len)) (and null (satisfies check-len)))。类型检查时两个分支会分别求值,每个分支都会调用一次check-len,因此会出现两次谓词调用的现象。
同时ANSI标准没有要求and类型说明符执行短路逻辑,实现也不会保证先执行所有基础类型检查再调用satisfies对应的谓词,所以就算前置的类型检查最终失败,谓词依然可能被执行。
相关规范依据
ANSI Common Lisp 标准对类型说明符的行为有以下明确约定:
and类型说明符仅表示所有子类型的交集,没有规定子类型的检查顺序,也不要求实现短路逻辑。satisfies类型说明符对应的谓词函数,实现可以在类型检查过程中任意次数调用,也不保证仅当前置类型条件满足时才调用该谓词。
因此依赖and类型的短路顺序、依赖satisfies谓词的调用次数都是不符合标准约定的,不同实现可能有完全不同的表现。
保证检查顺序的解决方案
1. 谓词内部增加前置检查
如果必须保留现有satisfies谓词的定义,在谓词内部先执行类型检查,自行实现短路逻辑,避免传入不符合要求的参数时抛出异常:
(defun check-len (x) ;; 先做类型检查,不符合直接返回nil,不会执行后续length调用 (and (listp x) (let ((res (> (length x) 1))) (format t "check len: ~a : ~a~%" x res) res)))
该方案下就算谓词被多次调用、或者传入非list类型的值,也不会出现异常。
2. 合并多步检查为单个谓词
对于需要固定顺序执行的多步类型检查,直接合并为一个独立的satisfies谓词,完全自己控制检查的顺序和短路逻辑,避免依赖实现的类型展开行为:
(defun valid-list-p (x) (and (listp x) (> (length x) 1))) ;; 类型说明符直接使用单个satisfies即可 (typep #(1 2 3) '(satisfies valid-list-p))
该方案是跨实现兼容的标准写法,不会出现非预期的调用行为。
3. 避免在satisfies谓词中添加副作用
标准不保证谓词的调用次数,因此不要在satisfies对应的谓词中添加打印、修改全局状态等副作用逻辑,避免出现不符合预期的表现。
内容的提问来源于stack exchange,提问作者leetwinski
相关产品推荐
相关产品推荐

