如何将 CLOS 用于类型而不是实例?
How to use CLOS for types rather than instances?
想象一些基 class pgj-model
上面定义了很多方法,但没有插槽。现在考虑:
(defclass cat (pgj-model) ())
(let ((cat (make-instance 'cat)))
(ensure-backend cat)
(insert cat (obj "name" "Joey" "coat" "tabby")))
如果我们想自定义新的 class cat
我们可以像这样专门化一个方法:
(defmethod insert ((model cat) (object hash-table))
;; Do something interesting
)
这一切都很好。但是 pgj-model
和 cat
都没有任何槽,它们是无状态的。这是设计使然,因为我只对它们感兴趣,因为它们是方法可以专门用于的 lisp 类型。所以似乎 annoying/confusing 在任何你想调用此类方法的地方创建 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
专业化程序来调度符号标识而不是 class:
(defgeneric insert (thing container))
(defmethod insert ((thing (eql 'cat)) (container hash-table))
...)
一种可能的方法是创建自己的单例元class,但我不建议这样做,因为这不是好的做法,这样您就可以将 allocate-instance
专门化为 call-next-method
第一次并将新实例缓存到 return 每隔一次。
(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))
请注意,您需要为每个 class 声明元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)
此代码不符合预期。
所以,我的实际建议是继续使用全局变量,或者更确切地说是全局 reader 函数,例如不可设置的 (cat)
.
想象一些基 class pgj-model
上面定义了很多方法,但没有插槽。现在考虑:
(defclass cat (pgj-model) ())
(let ((cat (make-instance 'cat)))
(ensure-backend cat)
(insert cat (obj "name" "Joey" "coat" "tabby")))
如果我们想自定义新的 class cat
我们可以像这样专门化一个方法:
(defmethod insert ((model cat) (object hash-table))
;; Do something interesting
)
这一切都很好。但是 pgj-model
和 cat
都没有任何槽,它们是无状态的。这是设计使然,因为我只对它们感兴趣,因为它们是方法可以专门用于的 lisp 类型。所以似乎 annoying/confusing 在任何你想调用此类方法的地方创建 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
专业化程序来调度符号标识而不是 class:
(defgeneric insert (thing container))
(defmethod insert ((thing (eql 'cat)) (container hash-table))
...)
一种可能的方法是创建自己的单例元class,但我不建议这样做,因为这不是好的做法,这样您就可以将 allocate-instance
专门化为 call-next-method
第一次并将新实例缓存到 return 每隔一次。
(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))
请注意,您需要为每个 class 声明元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)
此代码不符合预期。
所以,我的实际建议是继续使用全局变量,或者更确切地说是全局 reader 函数,例如不可设置的 (cat)
.