lisp: How to create temporary method specialization within a scope
Asked Answered
C

2

6

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?

Chemosmosis answered 27/4, 2011 at 13:43 Comment(5)
Method definitions are global, so adding and removing methods in the lexical or dynamic environment will affect other threads (assuming multiprocessing).Sightseeing
hmm, shit... unless... well in my use case i want to specialize on a specific instance, so if the instance is shared then all threads will be affected which is what I want, and if it is not shared, the dynamic method will not be applicable. Though I hoped for like a more abstract "tool-like" wrapper... Thanks for the ringing the bell.Chemosmosis
To globally enable logging for a specific instance, consider using change-class to change the class of the instance to a subclass with a my-meth :around method that performs logging, then disable logging by changing back to the original class. Use a macro to write the abstract wrapper.Sightseeing
You are full of ideas :) thanks :)Chemosmosis
I un-accepted Terje's answer because of 2 reasons: 1.I realized it would be misleading to anyone that will look at it in the future. What I meant to accept was his comment about CHANGE-CLASS. 2.I am starting to believe in the add-method scenario more firmly, since (if it eventually works) it can be a simple macro without any extra meta-class fuss, although admittedly a small fuss.Chemosmosis
S
5

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.

Sightseeing answered 27/4, 2011 at 15:27 Comment(2)
Probably this is what I will do if add/remove-method doesnt work, but i hoped for a totally dynamic way...Chemosmosis
Consider using macros for syntactic sugar that allows you to code the "method redefinitions" the way you'd like to do, even if the underlying implementation is a lesser hack.Sightseeing
K
3

It appears that there was once generic-flet and generic-labels special forms proposed for Common Lisp, but it was deleted from the final specs because it was deemed to be rarely supported by implementations and poorly designed. See Issue GENERIC-FLET-POORLY-DESIGNED Writeup in the HyperSpec. It's interesting reading the discussions why people thought lexical methods were less useful than lexically scoped functions.

So, without those, I don't really think there's a way to create lexically scoped method, though I haven't played with ContextL that Terje linked to in another answer so it's possible that will provided what you need.

Kansu answered 27/4, 2011 at 15:39 Comment(1)
The reasons really dont exist, for me at least. For once, binding the usefulness of localizing the generic functions with the usefulness of (the idea of) localizing class definition makes no sense to me. It seemed they just wanted a standard in implementation which was non existent. For me this is the only rational reason to remove them. But still, I need this, or to put it more sincerely, I would love it :) I think my use case is a good one.Chemosmosis

© 2022 - 2024 — McMap. All rights reserved.