CLOS中如何指定方法参数为特定类型(如字符串)的列表
CLOS中指定元素为特定类型的列表作为方法参数的方法
ANSI标准定义的CLOS参数特化符原生仅支持两种形式:
- 类名(比如
list、string、integer) (eql 常量值)形式的等值特化
你示例中写的(every 'string)这种基于列表元素类型的复合特化语法,无法直接在标准defmethod语法中使用,可以通过以下几种方式实现同等约束效果:
1. 运行时类型校验(最简便,无兼容问题)
这是日常开发最常用的方案:参数先特化为通用list类型,在方法入口处做类型校验即可。
可以先自定义对应的复合类型,配合内置的check-type宏做校验,代码可读性更好:
;; 定义「元素全为字符串的列表」类型 (deftype string-list () `(and list (satisfies (lambda (lst) (every #'stringp lst))))) (defmethod m1 ((x list)) ;; 入口处校验参数类型 (check-type x string-list "元素全为字符串的列表") (dolist (y x) (print (char y 0))))
如果只是单次使用,不需要单独定义类型,直接在方法里判断也可以:
(defmethod m1 ((x list)) (unless (every #'stringp x) (error "传入参数必须是所有元素为字符串的列表")) (dolist (y x) (print (char y 0))))
2. 抽离公共校验逻辑
如果多个方法都需要用到同一种列表元素类型约束,可以把校验逻辑抽到:before辅助方法里,避免重复写校验代码:
;; 先定义好string-list类型 (defmethod m1 :before ((x list)) (check-type x string-list)) ;; 后续所有m1针对list的主方法,都会自动触发参数校验 (defmethod m1 ((x list)) (dolist (y x) (print (char y 0))))
3. 扩展CLOS语法支持原生特化(接近你给出的示例写法)
如果你就是想要实现示例中直接把(every 'string)写在参数特化位的效果,需要通过CLOS MOP(元对象协议)自定义特化元类,扩展defmethod的特化符解析逻辑。
注意MOP不属于ANSI Common Lisp标准内容,不同Lisp实现(SBCL、CCL、CLISP等)的MOP接口存在细微差异,生产环境可以直接使用现成的MOP扩展库实现该能力,不需要从零编写元对象逻辑,最终可以支持如下写法:
;; 扩展后可直接使用你预期的语法 (defmethod m1 ((x (every 'string))) (dolist (y x) (print (char y 0))))
注意:不建议为了实现该约束专门定义包装类包裹普通列表,这种写法会破坏列表的原生操作兼容性,属于过度设计,绝大多数场景用前两种方案完全足够。
内容的提问来源于stack exchange,提问作者Tim Menzies
相关产品推荐
相关产品推荐

