Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

how to call specific method of generic function

118 views
Skip to first unread message

Tamas Papp

unread,
Apr 14, 2012, 8:58:23 AM4/14/12
to
Hi,

I am solving a problem using a numerical method, but for a specific
case an analytical solution exists. The solution will be implemented
by a generic function. I thought it would be nice to test my method
by comparing the analytical and numerical solutions for the specific
case, but I for that I need to force the numerical solution when CLOS
would dispatch to the analytical one.

Example:

(defgeneric addto5 (arg)
(:documentation "self-contained-example, dumbed down")
(:method (arg)
;; general
(+ 5 arg))
(:method ((arg (eql 0)))
;; specific
5))

I want to test something like

(= (addto5 0) ; want to call general, but don't know how
(addto5 0)) ; specific

I can, of course, always write the numerical and the analytical
implementations as plain vanilla functions and have the generic
function call those, but it would be so nice to have a simple code in
the main library (at the price of more complex code in the unit tests
-- I imagine that I would need to use MOP for this?)

Best,

Tamas

Pascal Costanza

unread,
Apr 14, 2012, 11:03:26 AM4/14/12
to
When you have the MOP, you can use generic-function-methods to obtain
the methods that are associated with the generic function:

> (defgeneric addto5 (arg)
(:documentation "self-contained-example, dumbed down")
(:method (arg)
;; general
(+ 5 arg))
(:method ((arg (eql 0)))
;; specific
5))
#<STANDARD-GENERIC-FUNCTION ADDTO5 21B6E25A>

> (generic-function-methods #'addto5)
(#<STANDARD-METHOD ADDTO5 NIL (T) 21B6E0AF>
#<STANDARD-METHOD ADDTO5 NIL ((EQL 0)) 21B6E14B>)

method-function reveals the method bodies:

> (mapcar #'method-function *)
(#<interpreted function (METHOD ADDTO5 (T)) 21B5D0DA>
#<interpreted function (METHOD ADDTO5 ((EQL 0))) 21B6E64A>)

...and those functions can be funcalled:

> (mapcar (lambda (f) (funcall f 0)) *)
(5 5)

Note, however, that Common Lisp implementations differ in what argument
lists method functions accept. The above example was in LispWorks, which
does not conform to the CLOS MOP specification here. The correct
invocation should be this:

> (mapcar (lambda (f) (funcall f '(0) '())) *)

I strongly believe this should work in SBCL, for example, but I haven't
tested it.

[According to the CLOS MOP, a method function receives the list of
arguments plus a list of "next" methods that call-next-method can invoke.]


Pascal

--
My website: http://p-cos.net
Common Lisp Document Repository: http://cdr.eurolisp.org
Closer to MOP & ContextL: http://common-lisp.net/project/closer/
The views expressed are my own, and not those of my employer.

Mirko Vukovic

unread,
Apr 15, 2012, 4:43:53 PM4/15/12
to
This is only tangentially related to your question, but it may help (Pascal's answer definitely enlightened me).

I also have multiple methods calculating the same thing, using different models or approximations.

For that I use a combination of methods, special variables, and functions. Using your example, I would have addto5% method, *addto5-default-method* special variable and addto5 function:

(defgeneric addto5% (method arg)
(:documentation "Add 5, using specified METHOD")
(:method (method arg)
(declare (ignore method))
;; general
(+ 5 arg))
(:method ((method (eql :binary)) arg)
(declare (ignore method))
(+ arg #b101)))

(defparameter *addto5-default-method* :binary
"Specify default addto5 method. Valid values are NIL or :BINARY")

(defun addto5 (arg)
(addto5% *addto5-default-method* arg))

At the user level, I use addto5 & *addto5-default-method*. But internally, I may use addto5%.

It is a bit clunky, but with a macro, one can hide some of the repetitive details.

Mirko

Pascal Costanza

unread,
Apr 15, 2012, 7:04:49 PM4/15/12
to
> At the user level, I use addto5& *addto5-default-method*. But internally, I may use addto5%.
>
> It is a bit clunky, but with a macro, one can hide some of the repetitive details.

This is an example which can be more cleanly expressed with ContextL:

> (define-layered-function addto5 (arg))
#<LAYERED-FUNCTION ADDTO5 200A19E2>

> (define-layered-method addto5 (arg)
(+ 5 arg))
#<LAYERED-METHOD ADDTO5 T NIL (#<BUILT-IN-CLASS T 20A8F65F>) 21E05533>

> (deflayer float)
#<STANDARD-LAYER-CLASS FLOAT 200A7E23>

> (define-layered-method addto5 :in-layer float (arg)
(+ arg 5.0))
#<LAYERED-METHOD ADDTO5 #<LAYER FLOAT 200FEB67> NIL (#<BUILT-IN-CLASS T
20A8F65F>) 200FF327>

> (addto5 6)
11

> (with-active-layers (float)
(addto5 6))
11.0

tar...@google.com

unread,
Apr 16, 2012, 9:25:14 PM4/16/12
to
On Saturday, April 14, 2012 5:58:23 AM UTC-7, Tamas wrote:
> Example:
>
> (defgeneric addto5 (arg)
> (:documentation "self-contained-example, dumbed down")
> (:method (arg)
> ;; general
> (+ 5 arg))
> (:method ((arg (eql 0)))
> ;; specific
> 5))
>
> I want to test something like
>
> (= (addto5 0) ; want to call general, but don't know how
> (addto5 0)) ; specific

Well, if you want something general to use for the case where all of the methods up the generalization hierarchy are supposed to return the same value, you could build this out of COMPUTE-APPLICABLE-METHODS, FUNCALL and the MOP accessor METHOD-FUNCTION. This version works with SBCL. You will need to adjust the package name on METHOD-FUNCTION for other lisps.

(defun check-all-methods (function &rest args)
(setq function (coerce function 'function))
(let ((all-methods (compute-applicable-methods function args)))
(reduce #'=
(loop for (method . next-methods) on all-methods by #'cdr
collect (funcall (sb-mop:method-function method)
args
next-methods)))))

Nicolas Neuss

unread,
Apr 17, 2012, 2:11:20 AM4/17/12
to
tar...@google.com writes:

> (defun check-all-methods (function &rest args)
> (setq function (coerce function 'function))
> (let ((all-methods (compute-applicable-methods function args)))
> (reduce #'=

Good idea, but you probably want APPLY here.

> (loop for (method . next-methods) on all-methods by #'cdr
> collect (funcall (sb-mop:method-function method)
> args
> next-methods)))))

Nicolas

tar...@google.com

unread,
Apr 17, 2012, 8:13:57 PM4/17/12
to
On Monday, April 16, 2012 11:11:20 PM UTC-7, Nicolas Neuss wrote:
> tar...@google.com writes:
>
> > (defun check-all-methods (function &rest args)
> > (setq function (coerce function 'function))
> > (let ((all-methods (compute-applicable-methods function args)))
> > (reduce #'=
>
> Good idea, but you probably want APPLY here.

Correct. I spaced out and was trying to avoid the arglist length. But that doesn't work here since #'= returns T or NIL and not a number.

niko...@random-state.net

unread,
Apr 18, 2012, 6:57:59 AM4/18/12
to
On Saturday, 14 April 2012 15:58:23 UTC+3, Tamas wrote:

> I want to test something like
>
> (= (addto5 0) ; want to call general, but don't know how
> (addto5 0)) ; specific
>
> I can, of course, always write the numerical and the analytical
> implementations as plain vanilla functions and have the generic
> function call those, but it would be so nice to have a simple code in
> the main library (at the price of more complex code in the unit tests
> -- I imagine that I would need to use MOP for this?)

The way I would probably do this is via ADD-METHOD/REMOVE-METHOD:

(defmacro without-specialized-methods ((fname) &body body)
`(call-without-specialized-methods
(lambda () ,@body)
#',fname))

(defun call-without-specialized-methods (thunk gf)
(let ((class-t (find-class t))
(removed nil))
(unwind-protect
(progn
(dolist (m (sb-mop:generic-function-methods gf))
(unless (every (lambda (s)
(eq s class-t))
(sb-mop:method-specializers m))
(push m removed)
(sb-mop:remove-method gf m)))
(funcall thunk))
(dolist (m removed)
(sb-mop:add-method gf m)))))

Completely untested. Same approach could be used to write

(without-method (spec1 spec2)
...body...)

removing the method specialized on SPEC1 and SPEC2 for BODY, etc.

This /is/ a bit heavy-handed, but you are testing the internals of your own code, so...

I also suspect that the bits of MOPery required here are extremely portable. I would expect no trouble just using CLOSER-MOP for this. (I would expect no trouble using CLOSER-MOP for most anything, really, but simple and straightforward things like this should be completely trouble-free.)

Cheers,

-- nikodemus
0 new messages