如何基于Common Lisp类定义可用于typecase及相关表达式的类型?
Common Lisp中类与类型的关联及自定义字符串类建模问题
一、自定义类在typecase中无法直接使用的问题
我定义了如下自定义类:
(defclass my-string () ((data :initarg :data :type simple-string) (properties :initarg :properties :type interval-tree)) (:documentation "Represents a string with properties."))
尝试在typecase中把my-string作为类型使用时,编译报错:
(defun upcase (obj) (typecase obj (my-string (string-upcase (slot-value obj 'data))) (string (string-upcase obj)) (integer (char-upcase (code-char obj))) (character (char-upcase obj)) (otherwise (error "Wrong type argument ~S" obj))))
我错误地尝试用deftype声明类型:
(deftype my-string (s) (typep s 'my-string))
使用时必须写成(my-string obj),但SBCL标记该分支为不可达:
(defun upcase (obj) (typecase obj ((my-string obj) (string-upcase (slot-value obj 'data))) (string (string-upcase obj)) (integer (char-upcase (code-char obj))) (character (char-upcase obj)) (otherwise (error "Wrong type argument ~S" obj))))
二、结构体的对比:自动生成对应类型
改用defstruct时,初始的typecase代码可以正常运行:
(defstruct my-string data properties)
展开宏后看到生成了谓词函数:
(SB-C:XDEFUN MY-STRING-P :PREDICATE NIL (SB-KERNEL::OBJECT) (TYPEP SB-KERNEL::OBJECT 'MY-STRING))
疑惑结构体是如何内部关联类型,而自定义类无法直接做到这点?
三、相关问题:如何建模扩展string的自定义类
是像my-string这样用组合方式(内置字符串作为槽)更好,还是继承自内置string类?已知继承不是最佳方式,想了解正确的建模逻辑,同时深入理解Common Lisp中类型与类的关系。
解决方案与原理
1. 正确让自定义类在类型表达式中生效
Common Lisp中类本身确实对应一个类型,但问题出在编译时可见性:defclass默认是运行时定义类,若编译typecase函数时类还未被加载,编译器无法识别该类型。解决方法是用eval-when确保类在编译、加载、运行阶段都可见:
(eval-when (:compile-toplevel :load-toplevel :execute) (defclass my-string () ((data :initarg :data :type simple-string) (properties :initarg :properties :type interval-tree)) (:documentation "Represents a string with properties.")))
之后即可直接在typecase中使用my-string作为类型,无需额外deftype声明:
(defun upcase (obj) (typecase obj (my-string (string-upcase (slot-value obj 'data))) (string (string-upcase obj)) (integer (char-upcase (code-char obj))) (character (char-upcase obj)) (otherwise (error "Wrong type argument ~S" obj))))
2. 结构体与类的类型关联差异
defstruct会在编译阶段自动完成以下操作:
- 注册对应的类型标识符
- 生成同名谓词函数(如
my-string-p) - 让编译器直接识别该类型
而defclass是动态的,默认仅在运行时创建类,需要通过eval-when显式告知编译器提前处理类定义,才能在类型检查中生效。
3. 扩展string的最佳建模方式
优先选择组合而非继承,原因如下:
- 内置
string属于数组的特殊子类,继承自它的自定义类无法被原生字符串操作函数(如string-upcase)直接识别,需为每个函数单独定义方法,维护成本极高。 - 组合方式(用槽存储原生字符串)逻辑清晰:可明确区分对内部字符串的操作和对扩展属性的操作,灵活扩展功能的同时复用原生字符串的所有工具函数。
类型与类的核心关系
- 类是面向对象系统的核心,用于实例化对象和方法调度;类型则用于类型检查、编译优化和声明。
- 每个类对应一个类型,但类型系统还包含大量非类类型(如
(integer 0 10)、satisfies谓词类型等)。 - 用户自定义类只要保证编译时可见,就能直接在
typecase、typep等类型操作中使用类名。
内容的提问来源于stack exchange,提问作者myname
相关产品推荐
相关产品推荐

