如何将CLOS用于类型而不是实例?

时间:2015-01-10 04:15:24

标签: common-lisp

想象一下基类pgj-model,其上定义了许多方法,但没有插槽。现在考虑:

(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-modelcat都没有任何插槽,它们的状态较少。这是设计的,因为我只对它们感兴趣,因为lisp类型可以专注于方法。所以在你想要调用这样的方法的任何地方创建类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)为每个方法调用增加一些开销。

其他建议?

2 个答案:

答案 0 :(得分:3)

您可以使用eql特化器来分配符号标识而不是类:

(defgeneric insert (thing container))

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

答案 1 :(得分:1)

一种可能的方法,但我不建议,因为这不是一个好习惯,就是创建自己的单例元类,这样你就可以第一次专注allocate-instancecall-next-method并缓存新的实例每隔一段时间返回一次。

(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)

这是该代码所不期望的。

所以,我的实际建议是继续使用全局变量,或者更确切地说是全局读者函数,例如:一个不可设置的(cat)