Common Lisp如何仅解绑单个特化方法保留泛型函数其余方法
如何移除Common Lisp泛型函数的单个指定方法
问题场景
假设定义如下类:
(defclass mammal () ()) (defclass cat (mammal) ()) (defclass dog (mammal) ())
以及对应的make-sound方法:
(defmethod make-sound ((mammal mammal)) (print "yawn!")) (defmethod make-sound ((cat cat)) (print "meow!")) (defmethod make-sound ((dog dog)) (print "bark!"))
需求是移除针对dog类特化的make-sound方法,同时保留其余特化版本的方法。
已知fmakunbound会直接解绑整个泛型函数,删除其下所有关联方法,操作后必须重新编译剩余方法,在复杂类结构下使用成本极高。defmethod的本质是向泛型函数中添加方法,需要找到对应的逆操作来移除单个指定方法。
实现方法
Common Lisp标准原生提供了移除单个方法的函数remove-method,不会影响泛型函数下的其他方法。
调用remove-method需要传入两个参数:目标泛型函数对象、待移除的方法对象。
待移除的方法对象可以通过find-method匹配获取,它接收三个核心参数:泛型函数对象、方法限定符列表、特化类列表。
针对当前示例,移除dog特化方法只需要执行:
(remove-method #'make-sound (find-method #'make-sound '() (list (find-class 'dog))))
执行完成后,再给dog实例调用make-sound,会沿着继承链向上匹配到mammal类的方法,输出"yawn!",cat类和mammal类的原有方法完全不受影响。
如果需要实现你示例中mmakunbound的便捷调用语法,可以自行封装工具函数:
(defun mmakunbound (generic-function-name specializer-class-names &optional (qualifiers '())) (let ((gf (fdefinition generic-function-name)) (specializers (mapcar #'find-class specializer-class-names))) (remove-method gf (find-method gf qualifiers specializers)) (list generic-function-name (first specializer-class-names))))
封装后就可以按照预期的方式调用:
CL-USER> (mmakunbound 'make-sound '(dog)) (MAKE-SOUND DOG) CL-USER> (setq fido (make-instance 'dog)) #<DOG {100471A153}> CL-USER> (make-sound fido) "yawn!"
补充说明
- 你示例中写的调用后输出
"bark!"属于笔误,移除dog特化方法后,dog实例不再有自身专属的make-sound实现,会继承父类mammal的方法逻辑。 - 如果不确定待移除的方法是否存在,可以给
find-method传入:errorp nil参数,找不到匹配方法时会返回nil而不是直接报错。 - 如果要移除带有限定符的方法(比如
:before/:after/:around辅助方法),把对应限定符传入find-method的限定符列表参数即可,例如要移除dog类的:after方法,匹配时写(find-method #'make-sound '(:after) (list (find-class 'dog)))。 fmakunbound仅适合需要彻底删除整个泛型函数的场景,日常移除单个方法不要使用。
内容的提问来源于stack exchange,提问作者Dominik Mokriš
相关产品推荐
相关产品推荐

