lispcommon-lispclosmop

lisp: How to create temporary method specialization within a scope


In Common lisp: Redefine an existing function within a scope? the OP asked for something similar. But I want to create a method specializer, not a function. Essentially suppose that a method is defined such:

defmethod my-meth ((objA classA) (objB classB)) (...)

What I would like to do is (pseudocode):

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

The real use is that I want to create a temporary environment, where setf slot-value-using-class will be specialized on eql, essentialy creating a specific object's on-demand interception of its slot writing. (The purpose is to log somewhere the old and new slot values and then call next method.) I don't want to create a metaclass because I may want to intercept already instantiated standard objects.

Of course I tried it and it didn't work (because how do you DEFMETHOD in a LABELS?) but I wanted some more experienced people to verify that it is not doable in such a way and/or propose a suitable way.

Comments?

EDIT:

Daniel and Terje provide excellent links for widening my knowledge towards the possibilities but I want to push it a little more on the search for a more vanilla approach before going there. I 've been looking into doing an add-method upon entering the environment, that will specialize on eql, and performing a remove-method upon exiting. I 've not finished yet . If anyone has played with those , comments would be nice. Will keep the thread up-to-date.

EDIT 2: I am close to do it with add-method scenario but there is a problem. Here is what I have tried:

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

Calling (my-test) with some object slot and data as args results in:

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

So I am stuck here. Specialization works since applicable methods now include the eql-specialized :after method, but unfortunately it does not seem to get called. Can anyone help so I can finish with it and refactor it to a sweet little utility macro?


Solution

  • No, you cannot define a dynamic extent or lexically scoped specialized method in Common Lisp.

    Aspect Oriented Programming can be used as an approach to solve the underlying problem. See also Context-Oriented Programming.

    ContextL is a library that provides aspect/context -oriented extensions for Common Lisp / CLOS.

    A light-weight alternative is to use a special/dynamic variable to indicate when the method should do the logging:

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

    Note though that the :around method will be called also when logging is disabled.