考虑一下:
(defclass my-string ()
((data :initarg :data :type simple-string)
(properties :initarg :properties :type interval-tree))
(:documentation
"Represents a string with properties."))
我想使用 my-string 作为 typecase 表达式中的类型:
(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 my-string (s) (typep s 'my-string))
看来我必须像这样使用(否则我会得到编译错误,typedef需要一个参数):
(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))))
但是 SBCL 将我的代码删除为无法访问! :-)
我该怎么做?如何正确声明类的类型,以便我可以在可以使用类型声明的表达式中使用它?
我知道我可以使用结构而不是类,在这种情况下,编译器会为该类型生成代码,并且初始类型“正常工作”:
(defstruct my-string
data properties)
宏观展开并查看生成的代码并没有真正让人更加开明。我看到这个:
(SB-C:XDEFUN MY-STRING-P
:PREDICATE
NIL
(SB-KERNEL::OBJECT)
(TYPEP SB-KERNEL::OBJECT 'MY-STRING))
这正是我所做的,但我不认为该功能在剧中,或者我错了?要么它们以某种方式在内部将结构与类型关联起来,要么我错过了生成代码的一些相关部分?我对这一切都不太熟悉,所以也很有可能。
一个相关的问题:对扩展 CommonLisp 中的字符串等内置类型的自定义类进行建模的正确方法是什么?这是一种更好的组合方法(如 my-string 中所做的那样),还是从某些内置 CL 字符串类继承可能更好?基于这个 SX 问题和答案,在我看来,继承并不是建模类型的最佳方式,这可能是为了更好,因为我们不喜欢 Java 或旧 C++ 中已知的分类法。
最后,在这种情况下,如果我制作一个专门针对这些类型的泛型方法,我什至不需要 typecase,整个问题就会消失,我完全意识到这一点。但是,我想了解有关类型和类的更多信息,以及如何在 CL 中对上述内容进行建模。
抱歉,如果它有点长,并且问题太多,我正在学习这个。感觉就像我遇到的每个问题以及尝试寻找答案都会引发另外 10 个问题:)。
对于类行为,您最好使用 Common Lisp 的多重分派功能 - 通过使用通用函数宏
defgeneric
和 defmethod
。
无论如何,使用调度的好处是你更加灵活,并且类明智的选择可以在以后扩展(更利于维护)。不仅如此 - 如果您导入此代码(例如作为包) - 在包中使用此代码的代码 - 仍然可以扩展调度(为调度添加新案例和新类),这在编程中非常强大。
(defclass my-string ()
((data :initarg :data :type simple-string)
(properties :initarg :properties :type interval-tree))
(:documentation
"Represents a string with properties."))
您首先使用
defgeneric
编写一个通用函数:
(defgeneric upcase (obj)
(:documentation "A generic function to upcase different types of objects."))
然后使用
defmethod
将每个 auf 子句编写为单独的方法函数:
(defmethod upcase ((obj my-string))
(string-upcase (slot-value obj 'data)))
(defmethod upcase ((obj string))
(string-upcase obj))
(defmethod upcase ((obj integer))
(char-upcase (code-char obj)))
(defmethod upcase ((obj character))
(char-upcase obj))
(defmethod upcase ((obj t))
(error "Wrong type argument ~S" obj))
通过以下方式尝试代码:
(let ((custom-string (make-instance 'my-string :data "hello" :properties nil)))
(format t "Upcased my-string: ~A~%" (upcase custom-string)))
(format t "Upcased plain string: ~A~%" (upcase "world"))
(format t "Upcased integer: ~A~%" (upcase 97)) ; ASCII code for 'a'
(format t "Upcased character: ~A~%" (upcase #\a))
(handler-case
(upcase '(1 2 3))
(error (e) (format t "Error: ~A~%" e)))
;; The output should be:
Upcased my-string: HELLO
Upcased plain string: WORLD
Upcased integer: A
Upcased character: A
Error: Wrong type argument (1 2 3)