4 methods inheritance common-lisp reusability clos
比方说,有一种通用的方法incx.有两个版本incx.一个专门的类型a,一个专门的类型b.Type b是的子类a.您将获得一个类型的对象b,派生类型 - 但您想要调用专用于类型的方法a.如果还没有一个专门针对类型的同名方法,你可以很容易地做到这一点b,但是,有这样的方法.
那么a在这种情况下如何调用专用于类型的方法呢?
(defclass a () ((x :accessor x :initform 0)))
(defclass b (a) ((y :accessor y :initform 0)))
(defgeneric inc (i))
(defmethod inc ((i a)) (incf (x i)))
(defmethod inc ((i b)) (incf (y i)))
(defvar r (make-instance 'b))
Run Code Online (Sandbox Code Playgroud)
正如CLOS所承诺的,这称为最专业的方法:
* (inc r)
* (describe r)
..
Slots with :INSTANCE allocation:
X = 0
Y = 1
Run Code Online (Sandbox Code Playgroud)
但在这种特殊情况下,(不是一般)我想要的是访问不太专业的版本.说出类似的话:
(inc (r a)) ; crashes and burns of course, no function r or variable a
(inc a::r) ; of course there is no such scoping operator in CL
Run Code Online (Sandbox Code Playgroud)
我看到该call-next-method函数可以在专门的方法中使用,以获得下一个不太专业的方法,但这不是这里想要的.
在这个被删除的代码中,我确实需要类似的东西call-next-method,但是用于调用补充方法.我们需要调用其补充方法,而不是在下一个不太专业的类中调用同名方法,而是使用不同的名称.补充方法也是专门的,但调用这个专门的版本是行不通的 - 原因call-next-method可能与之相同.并非总是专用于超类的必需方法具有相同的名称.
(call-next-method my-complement) ; doesn't work, thinks my-complement is an arg
Run Code Online (Sandbox Code Playgroud)
这是另一个例子.
有一个描述电子属性的基类和一个描述"奇怪电子"属性的派生类.专门研究特殊电子方法的奇怪电子的方法.为什么?因为这些方法为程序做正常的电子部分工作.奇怪电子的非电子部分几乎是微不足道的,或者更确切地说,如果它没有复制电子代码:
(defgeneric apply-velocity (particle velocity))
(defgeneric flip-spin (particle))
;;;; SIMPLE ELECTRONS
(defclass electron ()
((mass
:initform 9.11e-31
:accessor mass)
(spin
:initform -1
:accessor spin)))
(defmacro sq (x) `(* ,x ,x))
(defmethod apply-velocity ((particle electron) v)
;; stands in for a long formula/program we don't want to type again:
(setf (mass particle)
(* (mass particle) (sqrt (- 1 (sq (/ v 3e8)))))))
(defmethod flip-spin ((particle electron))
(setf (spin particle) (- (spin particle))))
;;;; STRANGE ELECTRONS
(defclass strange-electron (electron)
((hidden-state
:initform 1
:accessor hidden-state)))
(defmethod flip-spin ((particle strange-electron))
(cond
((= (hidden-state particle) 1)
(call-next-method)
;; CALL ELECTRON'S APPLY-VELOCITY HERE to update
;; the electron. But how???
)
(t nil)))
;; changing the velocity of strange electrons has linear affect!
;; it also flips the spin without reguard to the hidden state!
(defmethod apply-velocity ((particle strange-electron) v)
(setf (mass particle) (* (/ 8 10) (mass particle)))
;; CALL ELECTRON'S SPIN FLIP HERE - must be good performance,
;; as this occurs in critical loop code, i.e compiler needs to remove
;; fluff, not search inheritance lists at run time
)
Run Code Online (Sandbox Code Playgroud)
这一切都简化为一个简单的问题:
如果定义了更专业的方法,如何调用不太专业的方法?
Dir*_*irk 16
我更喜欢这里的明确方法:
(defun actually-inc-a (value) (incf (x value)))
(defun actually-inc-b (value) (incf (y value)))
(defmethod inc ((object a)) (actually-inc-a object))
(defmethod inc ((object b)) (actually-inc-b object))
Run Code Online (Sandbox Code Playgroud)
即,将要共享的实现部分放入单独的函数中.
(defun apply-velocity-for-simple-electron (particle v)
(setf (mass particle) (* (mass particle) (sqrt (- 1 (sq (/ v 3e8)))))))
(defun flip-spin-for-simple-electron (particle)
(setf (spin particle) (- (spin particle))))
(defmethod apply-velocity ((particle electron) v)
(apply-velocity-for-simple-electron particle v))
(defmethod flip-spin ((particle electron))
(flip-spin-for-simple-electron particle))
(defmethod apply-velocity ((particle strange-electron) v)
(setf (mass particle) (* (/ 8 10) (mass particle)))
(flip-spin-for-simple-electron particle))
(defmethod flip-spin ((particle strange-electron))
(when (= (hidden-state particle) 1)
(call-next-method)
(apply-velocity-for-simple-electron particle #| Hu? What's the V here? |#)))
Run Code Online (Sandbox Code Playgroud)
鉴于,我对电子一无所知,无论是普通的还是奇怪的,旋转与否,我都无法想到那些基本辅助函数的有意义的名称.但除此之外......
cor*_*ump 14
您的问题包含两个问题:
这个答案是我的另一个答案的合并,部分受到了Dirk对具体例子的良好答案的启发.我将首先讨论问题(调用特定方法)并解释为什么你应该尝试另一种方法,特别是你的例子.
是的,您可以调用与方法关联的函数而不是泛型函数.对于便携式方法,首先加载close-mop:
(ql:quickload :closer-mop)
Run Code Online (Sandbox Code Playgroud)
定义一些类和一个简单的泛型函数:
(defclass a () ())
(defclass b (a) ())
(defclass c (b) ())
(defgeneric foo (x)
(:method ((x a)) 0)
(:method ((x b)) (+ (call-next-method) 1))
(:method ((x c)) (* (call-next-method) 2)))
Run Code Online (Sandbox Code Playgroud)
我们有一个类层次结构(a <b <c)和仅在第一个参数上调度的泛型函数.
现在,我们计算类b的适用方法,并使用结果列表来定义一个函数,该函数调用foo专用于b的有效方法.
(destructuring-bind (method . next)
(closer-mop:compute-applicable-methods-using-classes
#'foo
(list (find-class 'b)))
(let ((fn (closer-mop:method-function method)))
(defun %foo-as-b (&rest args)
(funcall fn args next))))
Run Code Online (Sandbox Code Playgroud)
在这里你有两种不同的行为:
(let ((object (make-instance 'c)))
(list
(%foo-as-b object)
(foo object))
=> (1 2)
Run Code Online (Sandbox Code Playgroud)
但不建议这样做.CLOS提供了一种组合有效方法的方法,您应该尝试按预期使用它而不是劫持它.实际上,假设我评估以下内容:
(defmethod foo :before ((i a)) (print "Before A"))
Run Code Online (Sandbox Code Playgroud)
在foo通用的功能,叫上一个实例ç的ç,将打印字符串.但是当%foo-as-b在c上使用时,没有打印字符串,即使我们正在调用函数as-if c而不是b的实例, 并且该方法专门用于a.
这当然是因为compute-applicable-methods-using-classes取决于调用时已知的方法集.在这种情况下,函数%foo-as-b仍然使用过时的方法列表.如果您定义了多个此类函数或专门针对多个类,则效果会被放大.如果你想永远%foo-as-b与你的环境保持同步,那么你需要在每次调用这个函数时重新计算列表(而不是让let-over-lambda,你要重新计算lambda中的值).另一种可能性是在CLOS中引入钩子以在需要时重新计算函数,但这很疯狂.
考虑Liskov替代原则.过度使用继承来共享代码(即实现细节)而不是多态,这就是"赞成组合而不是继承"这样的建议.请参阅 "赞成组合而不是继承"的概念来自何处? 和 代码气味:继承滥用有关这方面的更多细节.
在C++中,base::method()可以找到,你只是调用一个具有相似名称的不同函数:当你告诉你的编译器要调用哪个方法时没有动态调度,所以这实际上是 - 如果你调用了常规函数.
根据您的要求,我会写下以下内容.它基于Dirk的版本,并使用辅助内联本地函数,当您想要避免重复时,它们是完全足够的:
(defclass electron ()
((mass :initform 9.11e-31 :accessor mass)
(spin :initform -1 :accessor spin)))
(defclass strange-electron (electron)
((hidden-state :initform 1 :accessor hidden-state)))
(let ((light-speed 3e8)
(mysterious-velocity 0d0))
(flet ((%flip (p)
(setf (spin p) (- (spin p))))
(%velocity (p v)
(setf (mass p)
(* (mass p)
(sqrt
(- 1 (expt (/ v light-speed) 2)))))))
(declare (inline %flip %velocity))
(defgeneric flip-spin (particle)
(:method ((p electron))
(%flip p))
(:method ((p strange-electron))
(when (= (hidden-state p) 1)
(call-next-method)
(%velocity p mysterious-velocity))))
(defgeneric apply-velocity (particle velocity)
(:method ((p electron) v)
(%velocity p v))
(:method ((p strange-electron) v)
(setf (mass p)
(* (/ 8 10) (mass p)))
(%flip p)))))
Run Code Online (Sandbox Code Playgroud)
这个问题已经解决了,希望可以解读:在CLOS中没有必要破解别的东西.可以很容易地识别由不同方法共享的辅助函数,如果需要重新编译它们,则必须重新编译整个表单,这样可以确保在所有方法中考虑类之间的现有耦合.
如果我们应用上述建议并使用构图,会发生什么?让我们改变你的意思strange-electron,它包含一个simple-electron.对于实际电子而言,这可能听起来很奇怪,但如果我们考虑用于模拟的对象,这是有意义的; 另外,请注意,在你的问题中,你实际上写的是"电子部分"和"奇怪电子的非电子部分".一,主要课程:
;; Common base class
(defclass electron () ())
;; Actual data for mass and spin
(defclass simple-electron (electron)
((mass :initform 9.11e-31 :accessor mass)
(spin :initform -1 :accessor spin)))
;; A strange electron with a hidden state
(defclass strange-electron (electron)
((simple-electron :accessor simple-electron :initarg :electron)
(hidden-state :initform 1 :accessor hidden-state)))
Run Code Online (Sandbox Code Playgroud)
注意strange-electron它不再继承simple-electron(我们不需要存储单独的质量和旋转)但包含一个实例simple-electron.另请注意,我们添加了一个公共electron基类,在这种情况下并不是绝对必要的.我将跳过我们定义泛型函数的部分,并仅描述方法.为了获得/设置那些奇怪电子的质量和旋转,我们必须委托给内部对象:
(macrolet ((delegate (fn &rest args)
`(defmethod ,fn (,@args (e strange-electron))
(funcall #',fn ,@args (simple-electron e)))))
(delegate mass)
(delegate spin)
(delegate (setf mass) new-value)
(delegate (setf spin) new-value))
Run Code Online (Sandbox Code Playgroud)
在我们继续之前,上面的代码是做什么的?如果我们扩展内部的最后一个形式macrolet,即带有的那个(setf spin),我们获得一个设置内部粒子的槽的方法:
(defmethod (setf spin) (new-value (e strange-electron))
(funcall #'(setf spin) new-value (simple-electron e)))
Run Code Online (Sandbox Code Playgroud)
那很棒.现在,我们可以定义flip-spin和apply-velocity很简单.基本行为与simple-electron班级有关:
(defmethod flip-spin ((e simple-electron))
(setf (spin e) (- (spin e))))
(defmethod apply-velocity ((e simple-electron) velocity)
(setf (mass e)
(* (mass e)
(sqrt
(- 1 (expt (/ velocity +light-speed+) 2))))))
Run Code Online (Sandbox Code Playgroud)
这与您原始问题中的方程式相同,但专门用于simple-electron.对于奇怪的电子,你依赖于内部对象:
(defmethod flip-spin ((e strange-electron))
(when (= (hidden-state e) 1)
(flip-spin (simple-electron e))
(apply-velocity (simple-electron e) 0d0)))
(defmethod apply-velocity ((e strange-electron) velocity)
(setf (mass e) (* (/ 8 10) (mass e)))
(flip-spin (simple-electron e)))
Run Code Online (Sandbox Code Playgroud)
你的目标之一是拥有一个CLOS接口,而不是一个"静态接口",这就是这里的情况.
明确地调用不太具体的方法是代码气味.在某些情况下,我不排除它可能是一种合理的方法,但我建议首先考虑替代设计.
常见的代码可以通过定期的功能共享,就像是一直在做(为方便定义总是).或者,更喜欢组合物.