2

How does one call a method object as a function?

Closer-mop and clos packages both provide method-function for turning a method object into a function. However, is there a way to do it without including another package? And if not, which package? (Using SBCL), but if a package is needed then how does the discrimination function do it?

Here is an example of using find-method to get a method object. The question is then how to call method-to-be-called.

(defclass a () ((x :accessor x :initform 0)))
(defgeneric inc (i))
(defmethod inc ((i a)) (incf (x i)))
(defvar r (make-instance 'a))

;; ... in a land far far away:    
(defvar method-to-be-called (find-method #'inc '() '(a)))

(funcall method-to-be-called r);; crashes and burns

As a secondary question, the docs say that the discrimination function first tries to compute-applicable-methods-by-class to find a method object, and if that fails, it uses compute-applicable-methods. Why do this two layer approach? Is it correct to assume the find-method is doing this two layer approach, so it is better to use find-method ?

-- Appendix -- In the comments below Rainer Joswig pointed out that this find-method form is implementation dependent:

(find-method #'inc '() '(a))) ; works on sbcl 1.3.1

He says the specifier list should be classes and suggests instead:

(find-method #'inc '() (list (find-class 'a))))

So I thought to just put my class in there:

(find-method #'inc '() (list a))  ; crashes and burns

Apparently (defclass a ... ) does not set a to a class. In fact it doesn't set it to anything!

* (defclass a () ((x :accessor x :initform 0)))
#<STANDARD-CLASS COMMON-LISP-USER::A>
* a

... The variable A is unbound.

However, this works:

* (defvar ca (defclass a () ((x :accessor x :initform 0))))
CA
* (defmethod inc ((i a)) (incf (x i)))
WARNING: Implicitly creating new generic function COMMON-LISP-USER::INC.
#<STANDARD-METHOD COMMON-LISP-USER::INC (A) {1005EE8263}>
enter code here
* (find-method #'inc '() (list ca))   
#<STANDARD-METHOD COMMON-LISP-USER::INC (A) {1005EE8263}>
* 

So a class is the return value from the defclass, not the value of the symbol that is provided to defclass.

coredump
  • 37,664
  • 5
  • 43
  • 77
  • 1
    You'll probably want to read more about the [MetaObject Protocol](http://alu.org/mop/index.html) if you're planning to mess around with it. Usually you wouldn't need to though. – jkiiski Feb 05 '16 at 10:03
  • Yes, read it and about 10 other docs. I must say there was a lot to absorb. There is also a lot of other literature on the net. One of the more interesting gave a history of how CLOS and MOP came to be. I find it all a bit cryptic. The MOP functions are some of the few that don't have examples in CHS. Also, no one has said, but it appears the answer to my question as posed is simple "you can't" - you need at least one external call, closer-mop:method-function. Seems this should have been mentioned in the spec, and perhaps a reason given. –  Feb 06 '16 at 08:12
  • 1
    MOP functions don't have examples in the hyperspec, because they are not part of the standard. The reason why you should use `closer-mop` is not that you can't achieve the same without it (of course you can, since otherwise `closer-mop` couldn't do it), but that it's non-standard, and thus implementation dependent. `closer-mop` provides you with an implementation independent way of doing it. There is no good reason not to use it, assuming you really need to in the first place (which you most likely don't). – jkiiski Feb 06 '16 at 08:33
  • 1) pedantic point 'MOP not in the standard', as there are meta object related functions in the standard. E.g. http://www.lispworks.com/documentation/HyperSpec/Body/f_comput.htm#compute-applicable-methods Notice there is no example given. 2) As for the rest of your comment, there is no standard way (using functions listed in the hyper-spec) to call a method object given the object (rather than a tag for its name). Most people would find this to being analogous to having defun without funcall. .. or please show me where, as I don't see it. Furthermore, to do it with closer-mop is involved. –  Feb 06 '16 at 09:26
  • @jkiski, here is a very interesting paper with a short history, it notes "Common Lisp as standardized only includes a very small portion of this metaobject protocol" http://www.doc.gold.ac.uk/~mas01cr/papers/ecoop2007/abstract.pdf Well anyway, I went looking for the analogy to fun-call, perhaps I'm just a mutant. Either way I handn't expected to discuss the justification for the question as much as had hoped to find an answer. Which I have, and I must thank you and the others who commented for the many insights on this and a related post #35171694. Thank you. –  Feb 06 '16 at 09:38

3 Answers3

4
(find-method #'inc '() '(a))

Above does not work. We need a list of classes, not a list of symbols.

(funcall (method-function (find-method #'inc
                                       '()
                                       (list (find-class 'a))))
         r)

Since the function method-function belongs to the MOP, many implementations provide it and it is in some implementation specific package. CLOSER-MOP makes it available, too.

But usually, if you are already trying extracting method functions, then you are probably using CLOS the wrong way or you are really knowing what you are doing...

Rainer Joswig
  • 136,269
  • 10
  • 221
  • 346
  • That's implementation specific, but not standard. – Rainer Joswig Feb 05 '16 at 10:31
  • excuse, deleted the comment showing my example find-method working so as to combine it with this comment. Yes, thanks for that improvement. As for the base question, when I run your example SBCL says that method-function is undefined. So I gather I have to include another package to get it. In addition it is not listed as a function in the Hyperspec. This is the gist of the question, note all that stuff listed about packages. Where did you get your method-function? Which LISP are you using? –  Feb 05 '16 at 10:41
  • @user244488 In sbcl it's in `sb-pcl`. You can find functions easily using [`apropos`](http://www.lispworks.com/documentation/HyperSpec/Body/f_apropo.htm). However, you should not use that. It's a non-standard extension, so your code will not be portable if you use it. – jkiiski Feb 05 '16 at 10:47
  • @user244488 As I mentioned, get `method-function` via Closer-MOP - that makes it more portable over various platforms. I'm using LispWorks, where METHOD-FUNCTION is a part of an extended Common Lisp package. – Rainer Joswig Feb 05 '16 at 10:49
  • @RainerJoswig will the Closer-MOP version perform differently? I know the sbcl folks have put a lot of work into optimizing, and the method calls look to be particularly painful. –  Feb 05 '16 at 16:35
  • 1
    @user244488 Closer-mop unifies different implementations of the meta-object protocol as defined in [AMOP](https://en.wikipedia.org/wiki/The_Art_of_the_Metaobject_Protocol). Sometimes the existing code in CL implementations already works as intended, and sometimes closer-mop needs to change/add things. In particular, in SBCL, `closer-mop:method-function` and `sb-pcl:method-function` identify the exact same function, the one implemented in SBCL. You can read "Fast generic dispatch for Common Lisp" by R. Strandh for work related to optimization. But the point of closer-mop is portability first. – coredump Feb 05 '16 at 17:18
  • @RainerJoswig, ah heck, I still have some problem making that solution of yours run: (funcall (closer-mop:method-function (find-method #'inc '() (list (find-class 'a)))) r) ---> invalid number of arguments: 1 –  Feb 06 '16 at 07:51
4

How does one call a method object as a function?

Honest question: why do you want to do that? Did you specify how the method's function is built in the first place, or not?

Even with closer-mop, I believe that the function returned by closer-mop:method-function is, at most, consistent with closer-mop:make-method-lambda in terms of its lambda-list, so perhaps you can use a package to know what you can count on portably.

A method's function does not have to be a function with the same lambda-list as the generic function, and usually it isn't due to next-method-p and call-next-method. Some implementations might use dynamic bindings for the next method list, so these might have a method lambda-list congruent with the generic function. Just don't count on it, generically.

I believe SBCL is not one of these implementations, the next method list is passed to the method's function to support next-method-p and call-next-method.

Why do this two layer approach?

Because it allows memoizing (or caching) based on the list of classes, when possible. If the generic function is called again with arguments of the same classes, and the generic function has not been updated (see "Dependent Maintenance Protocol" in the MOP), it can reuse the last result without further processing, for instance, by keeping the results in a hash table which keys are lists of classes.

However, if compute-applicable-methods-using-classes returns a false second value, then compute-applicable-methods is used. The reason is that no method could be found using classes alone, and this means some method has a non-class specializer.

This is different than saying there are no applicable methods, for instance, if all methods are specialized on classes and there are no applicable methods, compute-applicable-methods-using-classes should return an empty list and a true second value. There's no point in calling compute-applicable-methods, it won't (or rather, it shouldn't, if well implemented) find anything further.

It's still possible to perform memoization when compute-applicable-methods is used, but the memoization is no longer as trivial as, for instance, using a list of classes as a key in a hash table. Perhaps you could use a tree structure where you'd try to look up a method for each specializer (instance, then class) sequentially for each argument, until a tree node matched the whole specializable parameter list.

With non-standard specializers, you'd have to change the search order for each node. Unless such specializer's priority is not strictly before, between or after eql and a class, then you're in uncharted areas.

Actually, you'll have to change compute-applicable-methods-using-classes to recognize the non-standard specializers and return false early, and you'll have to change compute-applicable-methods to process these specializers, anyway, so perhaps you'll have a good knowledge on, if possible, how to memoize with the results of compute-applicable-methods anyway.

Is it correct to assume the find-method is doing this two layer approach, so it is better to use find-method ?

No, the purpose of find-method is to find a specific method, not an applicable method. It does not use compute-applicable-methods-using-classes or compute-applicable-methods at all. In fact, it couldn't use the latter ever, as it takes actual arguments instead of specializers.

acelent
  • 7,965
  • 21
  • 39
  • Honest, and not sarcastic followup: why not? I'm curious to know if there is a danger to keeping a method-function and then calling it later. Perhaps some dynamic updates to something would be missed? –  Feb 06 '16 at 06:55
  • Well, the MOP specification says that, by default, method functions accept a list of arguments (ultimately, from the generic function), a list of next methods and (not in the Common Lisp standard) any additional arguments provided in a `call-method` form. So, with a library that bridges the MOP specification for the underlying implementation, you may call a method function counting on this. As for the danger, it's like you say, (method redefinition or removal, generic function redefinition, etc.) plus the unknown non-standard additional arguments. – acelent Feb 08 '16 at 12:54
  • Another danger is if you call the method function using arguments that are not congruent with the method's specializers. An implementation that compiles to native code with type assumptions may try to access the slots of an object without checks (e.g. for null or subclass). So, if you call a method function with invalid arguments (e.g. an object that doesn't match the respective class specializer or `nil` where it's not expected), you may cause undefined behavior. – acelent Feb 08 '16 at 15:14
  • see question #35171694, for the reason this question was asked / about calling a method function. I happened upon a cleaner solution today, though probably lower performance, and will be posting it there. –  Feb 10 '16 at 02:38
  • I posted [an answer](http://stackoverflow.com/a/35313790/800524) on [that question](http://stackoverflow.com/q/35171694/800524) which you may find interesting, since it doesn't involve the MOP. – acelent Feb 10 '16 at 15:09
2

For the particular case of method-function, closer-mop for SBCL simply reexport the existing symbol from sb-pcl, as seen in closer-mop-packages.lisp. The whole file make use of read-time conditionals (see 1.5.2.1 Use of Implementation-Defined Language Features). That means that if you are working with SBCL, you might call sb-pcl:method-function (PCL means Portable Common Loops).

The generic function compute-applicable-methods-by-class allows you to know which methods are applicable given classes. This is useful if you don't have actual instances on which you can operate. It seems also that compute-applicable-methods-using-classes allows the implementation to memoize the applicable methods when the second return value is true. This generic method does not allow you to find applicable methods specialized with eql specializers.

I am speculating here, but it makes sense to fall back on compute-applicable-methods to allow for example eql-specializers or because it is slightly easier to define a method for compute-applicable-methods. Note the paragraph about consistency:

The following consistency relationship between compute-applicable-methods-using-classes and compute-applicable-methods must be maintained: for any given generic function and set of arguments, if compute-applicable-methods-using-classes returns a second value of true, the first value must be equal to the value that would be returned by a corresponding call to compute-applicable-methods. The results are undefined if a portable method on either of these generic functions causes this consistency to be violated.

I don't think there is a find-method-using-classes generic function specified anywhere.

coredump
  • 37,664
  • 5
  • 43
  • 77
  • I imagine then that find-method must be calling both in the same manner, and tie into the caching bit. Would be curious to see the implementation for it, but haven't dug into the sbcl sources yet. –  Feb 05 '16 at 10:48
  • The purpose of `find-method` is to find a **specific** method, not an **applicable** method, so it doesn't use `compute-applicable-methods`* at all. – acelent Feb 05 '16 at 17:44