考虑一下:
(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 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,如果我将 upcase 变成一个专门针对这些类型的通用方法,整个问题就消失了,我完全知道这一点。但是,我想更多地了解类型和类,以及如何在 CL 中对类似上述内容进行建模。
抱歉,这篇文章有点长,而且一个问题太多了,我才刚刚开始学习。感觉我遇到的每一个问题和寻找答案的尝试都会引出另外 10 个问题 :)。
您的使用
deftype
方式完全错误,因此,尽管有更好的答案,但我还是想关注错误的部分。当您编写 时
(deftype u (s) (typep s 'u))
,第一次您不会收到任何警告,但之后如果您再次尝试(使用 SBCL),则会在编译期间收到警告,因为u
中的(typep s 'u)
参数数量不符合 的定义(deftype u (s) ...)
。一旦我们理解了 的含义,就可以解释这一点deftype
。首先,为了使
(typep ...)
调用有意义,您需要编写类似这样的内容,(typep v '(u s))
其中v
是一个值,并且(u s)
是一个类型,由参数 参数化s
。例如,(integer -2 -2)
是这样一种类型,它是 的子集,integer
具有包含边界:带有 的值
T
是成功时的值typep
。这是因为您定义的函数deftype
必须返回类型表达式,通常您会发现类似以下内容:或者
通过上述定义,您可以检查数组是否实际上是整数的方阵:
产生类型说明符
square-matrix
的函数也是如此,如4.2.3 类型说明符中所定义。您的
my-string
函数返回一个广义布尔值(的结果typep
),而您想要给出的参数是一个值(类型为),my-string
而s
在deftype
参数中应该用来表达类型参数,因为您可能有不同的变化my-string
(例如,您的字符串的明确大小参数)。我希望这能使事情稍微澄清一些。
此外,类是一种类型,因此可以这样:
任何 CLOS 类都有相应的类型。
该类的类对象
FOO
具有类型STANDARD-CLASS
。该类的实例
FOO
属于类型FOO
。因此
TYPECASE
适用于 CLOS 类的类型名称:您最好使用 Common Lisp CLOS 的多重分派功能来实现类行为(在这种情况下,只需要单次分派) - 通过使用通用函数宏
defgeneric
和defmethod
。无论如何,使用调度的好处在于您可以更加灵活,并且类选择以后可以扩展(维护起来更好)。不仅如此 - 如果您导入此代码(例如作为包) - 使用此代码的代码(嵌入在包中的此代码) - 仍然可以扩展调度(为调度添加新案例/新类),这是编程中非常强大的功能。
并且调度能力优于
typecase
。typecase
当然速度更快。首先使用以下方法编写一个通用函数
defgeneric
:defmethod
然后,使用每个 auf 子句作为单独的方法函数进行编写:通过以下方式尝试代码:
@coredump 回答正确。
在某些情况下,通过
typecase
谓词进行调度可能更有意义。你可以编写一个宏:
然后像这样使用它:
但我知道这实际上不是问题所在。