Common lisp 如何对类型而不是实例使用CLO?

Common lisp 如何对类型而不是实例使用CLO?,common-lisp,Common Lisp,想象一下某个基类pgj模型,上面定义了许多方法,但没有插槽。现在考虑: (defclass cat (pgj-model) ()) (let ((cat (make-instance 'cat))) (ensure-backend cat) (insert cat (obj "name" "Joey" "coat" "tabby"))) 如果我们想定制新类cat,我们可以专门化如下方法: (defmethod insert ((model cat) (object hash-tabl

想象一下某个基类
pgj模型
,上面定义了许多方法,但没有插槽。现在考虑:

(defclass cat (pgj-model) ())

(let ((cat (make-instance 'cat)))
  (ensure-backend cat)
  (insert cat (obj "name" "Joey" "coat" "tabby")))
如果我们想定制新类
cat
,我们可以专门化如下方法:

(defmethod insert ((model cat) (object hash-table))
  ;; Do something interesting
)
这一切都很好。但是无论是
pgj模型
还是
cat
都没有任何插槽,它们都是无状态的。这是设计上的,因为我只对它们感兴趣,因为它们是方法可以专门化的lisp类型。因此,在您想要调用此类方法的任何地方创建class
cat
的实例似乎都很烦人/令人困惑

一个想法是:

(defparameter *cat* (make-instance 'cat))   ; There can be only one...
...
(insert *cat* (obj "name" "Joey" "coat" "tabby"))
另一种方法是在我的所有泛型函数上专门化一个附加方法,如下所示:

(defmethod insert ((model symbol) object)
  (insert (make-instance model) object))

(insert 'cat (obj "name" "Joey" "coat" "tabby"))
这看起来还可以,但1)可能会让用户感到困惑,2)用样板文件扩充通用函数,3)为每个方法调用增加一些开销


其他建议?

您可以使用
eql
专门化器根据符号标识而不是类进行分派:

(defgeneric insert (thing container))

(defmethod insert ((thing (eql 'cat)) (container hash-table))
  ...)

您可以使用
eql
specializer根据符号标识而不是类进行分派:

(defgeneric insert (thing container))

(defmethod insert ((thing (eql 'cat)) (container hash-table))
  ...)

一种可能的方法是创建您自己的单例元类,这样您就可以将
分配实例
专门化为
第一次调用下一个方法
,并缓存新实例以每隔一次返回一次,但我不建议这样做,因为这不是一种好的做法

(defclass singleton-class (standard-class)
  ((instance :initform nil :accessor singleton-class-instance)))

(defmethod allocate-instance ((class singleton-class) &rest initargs)
  (declare (ignore initargs))
  (with-slots (instance) class
    (or instance
        (setf instance (call-next-method)))))

(defclass pgj-model ()
  ()
  (:metaclass singleton-class))

(defclass cat (pgj-model)
  ()
  (:metaclass singleton-class))
注意,您需要为每个类声明元类,它不是继承的

您还可以以类似的方式专门化
make instance
,这样它在第一次之后就不会遵循通常的初始化过程

我没有处理同步,因为您的对象是无状态的,但如果您需要可靠地保留单个标识,则可能需要对其进行寻址

最后,由于破坏了
allocate instance
以及
make instance
的目的,这显然不是一个好的实践。根据规范,Lisp实现只需编译以下函数:

(defun test-singleton-class (class)
  (eq (make-instance class)
      (make-instance class)))
通过对其进行优化,就好像它是这样定义的:

(defun test-singleton-class (class)
  ;; possibly inlined make-instance calls
  nil)
这是此代码不需要的


因此,我的实际建议是继续使用全局变量,或者更确切地说是全局读取器函数,例如非setfable
(cat)

一种可能的方法,但我不建议,因为这不是一种好的做法,就是创建您自己的单例元类,因此,您可以在第一次将
分配实例
专门化为
调用下一个方法
,并缓存新实例以每隔一次返回一次

(defclass singleton-class (standard-class)
  ((instance :initform nil :accessor singleton-class-instance)))

(defmethod allocate-instance ((class singleton-class) &rest initargs)
  (declare (ignore initargs))
  (with-slots (instance) class
    (or instance
        (setf instance (call-next-method)))))

(defclass pgj-model ()
  ()
  (:metaclass singleton-class))

(defclass cat (pgj-model)
  ()
  (:metaclass singleton-class))
注意,您需要为每个类声明元类,它不是继承的

您还可以以类似的方式专门化
make instance
,这样它在第一次之后就不会遵循通常的初始化过程

我没有处理同步,因为您的对象是无状态的,但如果您需要可靠地保留单个标识,则可能需要对其进行寻址

最后,由于破坏了
allocate instance
以及
make instance
的目的,这显然不是一个好的实践。根据规范,Lisp实现只需编译以下函数:

(defun test-singleton-class (class)
  (eq (make-instance class)
      (make-instance class)))
通过对其进行优化,就好像它是这样定义的:

(defun test-singleton-class (class)
  ;; possibly inlined make-instance calls
  nil)
这是此代码不需要的


因此,我的实际建议是继续使用全局变量,或者更确切地说是全局读取器函数,例如非setfable
(cat)

你能解释一下为什么你认为这比上面的
defparameter
建议更好吗?@gtod:你不需要全局特殊变量,你只需要具有标识的东西。一个符号可以做到这一点。当看到方法定义或它的调用时,您只能看到“啊,一个
cat
”,而不是“那又是什么
*cat*
东西?”但是,如果我现在想要一只黑猫,那么使用这个
eql
系统,我是否不必去改变我所有现有方法的定义,让它们像对待猫一样对待黑猫?换句话说,您如何获得与
(defclass black cat(cat))
相同的效果,然后在新类上专门化
插入
,而不专门化
获取
更新
等?您没有。与比较对象相比,
eql
专门化器没有其他可能的专门化。如果你想这样做,请坚持使用单变量或全局变量。你能解释一下为什么你认为这比上面的
defparameter
建议更好吗?@gtod:你不需要全局特殊变量,你只需要具有标识的变量。一个符号可以做到这一点。当看到方法定义或它的调用时,您只能看到“啊,一个
cat
”,而不是“那又是什么
*cat*
东西?”但是,如果我现在想要一只黑猫,那么使用这个
eql
系统,我是否不必去改变我所有现有方法的定义,让它们像对待猫一样对待黑猫?换句话说,您如何获得与
(defclass black cat(cat))
相同的效果,然后在新类上专门化
插入
,而不专门化
获取
更新
等?您没有。与比较对象相比,
eql
专门化器没有其他可能的专门化。如果你想要的话,坚持使用单例变量或全局变量。很棒的东西。全局读卡器函数是指类似于
(let(cat)(defun cat()(if cat cat(setf cat)(make instance'cat'))
?为什么这比一个全局变量好?因为你可以在不改变使用它的代码的情况下实现它。如果以后决定需要使用全局/动态变量(例如,
*cat*
),使其值取决于当前线程,或者同步原语(比较和交换)对局部变量不起作用,则仍将访问该变量