Lisp:如何在作用域内创建临时方法专用化

6
Common Lisp: Redefine an existing function within a scope?中,提问者询问了类似的问题。但我想创建一个方法专用化程序,而不是函数。基本上,假设已定义了一个方法:
defmethod my-meth ((objA classA) (objB classB)) (...)

我想要做的是(伪代码):

(labels ((my-meth ((objA classA) (objB (eql some-object)))))
   do stuff calling my-meth with the object...)

真正的用途是我想创建一个临时环境,在这个环境中,setf slot-value-using-class将会在eql上进行特殊化,从而创建一个特定对象的按需拦截其插槽写入。 (目的是记录旧的和新的插槽值,然后调用下一个方法。)我不想创建一个元类,因为我可能想要拦截已经实例化的标准对象。
当然,我尝试过了,但它没有起作用(因为你如何在LABELS中进行DEFMETHOD?),但我想让更有经验的人验证它是否不可行和/或提出合适的方法。
有评论吗?
编辑:
Daniel和Terje提供了优秀的链接,扩大了我的知识范围,但我想在去那里之前再深入探索一下更普通的方法。 我一直在研究进入环境时执行add-method,该方法将专门针对eql,并在退出时执行remove-method。 我还没有完成。 如果有人已经尝试过,请发表评论。 将保持线程最新状态。
编辑2:我接近使用add-method方案完成它,但存在问题。 这是我尝试过的内容:
    (defun inject-slot-write-interceptor (object fun)
    (let* ((gf (fdefinition '(setf sb-mop:slot-value-using-class)))
            (mc (sb-mop:generic-function-method-class gf))
            (mc-instance (make-instance (class-name mc) 
                            :qualifiers '(:after)
                            :specializers (list (find-class 't)
                                                (find-class 'SB-PCL::STD-CLASS)
                                                (sb-mop::intern-eql-specializer object) 
                                                (find-class 'SB-MOP:STANDARD-EFFECTIVE-SLOT-DEFINITION))
                           :lambda-list '(new-value class object slot)
                           :function (compile nil (lambda (new-value class object slot) (funcall fun new-value class object slot))))))
         (add-method gf mc-instance)
         (defun remove-slot-write-interceptor ()
            (remove-method gf mc-instance))
       ))

(defun my-test (object slot-name data)
     (let ((test-data "No results yet") 
           (gf (fdefinition '(setf sb-mop::slot-value-using-class))))
       (labels ((show-applicable-methods () (format t "~%Applicable methods: ~a" (length (sb-mop:compute-applicable-methods gf (list data (class-of object) object (slot-def-from-name (class-of object) slot-name)))))))
            (format t "~%Starting test: ~a" test-data)
            (show-applicable-methods)
            (format t "~%Injecting interceptor.")
            (inject-slot-write-interceptor object (compile nil (lambda (a b c d) (setf test-data "SUCCESS !!!!!!!"))))
            (show-applicable-methods)
            (format t "~%About to write slot.")
            (setf (slot-value object slot-name) data)
            (format t "~%Wrote slot: ~a" test-data)
            (remove-slot-write-interceptor)
            (format t "~%Removed interceptor.")
            (show-applicable-methods)
       )))      

当使用对象插槽和数据作为参数调用 (my-test) 时,结果如下:

Starting test: No results yet 
Applicable methods: 1 
Injecting interceptor. 
Applicable methods: 2 
About to write slot.     
Wrote slot: No results yet  <----- Expecting SUCCESS here....
Removed interceptor. 
Applicable methods: 1

我卡在这里了。专业化(Specialization)可以使用,因为现在适用的方法包括eql-specialized:after方法,但不幸的是它似乎没有被调用。有人能帮忙吗,这样我就可以完成它并将其重构为一个简单的实用宏了。


方法定义是全局的,因此在词法或动态环境中添加和删除方法将影响其他线程(假设使用了多进程)。 - Terje Norderhaug
嗯,糟糕...除非...在我的使用情况中,我想专门针对特定实例进行优化,因此如果该实例是共享的,则所有线程都会受到影响,这正是我想要的,如果它不是共享的,则动态方法将无法应用。虽然我希望有一个更抽象的“工具型”包装器...感谢提醒。 - Paralife
1
全局启用特定实例的日志记录,考虑使用change-class将实例的类更改为带有执行日志记录的“my-meth :around”方法的子类,然后通过改回原始类来禁用日志记录。使用宏编写抽象包装器。 - Terje Norderhaug
你充满了创意 :) 谢谢 :) - Paralife
我取消了对Terje答案的接受,原因有两个:1.我意识到这会误导未来查看它的任何人。我本意是要接受他关于CHANGE-CLASS的评论。2.我开始更坚定地相信add-method场景,因为(如果最终有效)它可以成为一个简单的宏,而不需要任何额外的元类麻烦,尽管承认有一点点麻烦。 - Paralife
2个回答

5

不,你不能在Common Lisp中定义动态范围或词法作用域的专门方法。

面向方面编程可以作为解决基本问题的一种方法。另请参见面向上下文编程

ContextL是一个提供了针对Common Lisp/CLOS的方面/上下文扩展的库。

一个轻量级的替代方法是使用特殊/动态变量来指示方法何时应该记录日志:

(defparameter *logging* NIL "Bind to a true value to activate logging")

(defmethod my-meth :around ((objA classA) (objB (eql some-object)))
  (prog2 
   (when *logging*
     (logging "Enter my-meth"))
   (call-next-method)
   (when *logging*
     (logging "Exit my-meth"))))

(let ((*logging* T))
   (do stuff calling my-meth with the object...))

请注意,即使禁用日志记录,:around方法也将被调用。

如果添加/删除方法不起作用,我可能会这样做,但我希望有一种完全动态的方式... - Paralife
考虑使用宏来实现语法糖,这样可以让你以自己喜欢的方式编写“方法重定义”,即使底层实现是一个较小的hack。 - Terje Norderhaug

3
看起来曾经有一些针对Common Lisp提出的特殊形式generic-fletgeneric-labels,但是由于实现支持度低且设计不佳而被删除。请参见HyperSpec中的Issue GENERIC-FLET-POORLY-DESIGNED Writeup。有趣的是阅读人们为什么认为词法方法比词法作用域函数不太有用的讨论。

因此,在没有这些特殊形式的情况下,我真的不认为有办法创建词法作用域方法,尽管我还没有尝试过Terje在另一个答案中链接到的ContextL,所以可能会提供您需要的内容。


原因对我来说并不存在。一方面,将本地化通用函数的有用性与(本地化类定义的想法的)有用性绑定在一起对我来说毫无意义。似乎他们只是想要一个不存在的实现标准。对我来说,这是唯一合理的删除它们的原因。但是,我仍然需要它,或者更真诚地说,我会喜欢它 :) 我认为我的用例很好。 - Paralife

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接