2

Let's say I write the following piece of code (please forgive any errors, I'm a Lisp newbie and I can't run CL on this machine)

(defclass o () ())
(defclass a (o) ())
(defclass b (o) ())
(defgeneric m (x))
(defmethod m ((x o))
   (print "O")
)
(defmethod m ((x a))
   (print "A")
   (call-next-method (make-instance 'a))
)
(defmethod m ((x b))
   (print "B")
   (call-next-method (make-instance 'b))
)

(setq aa (make-instance 'a))
(m aa) ;prints A, then O
(setq bb (make-instance 'b))
(m bb) ;prints B, then O

According to my expectations, it should print what's written in the comments without any complaints.

What will happen if I add the following code?

(defclass c (a b) ())
(setq cc (make-instance 'c))
(m cc)

If I understand standard method combination, the applicable methods for cc will be sorted as (m a), (m b), (m o), and (m b) will not be called by call-next-method successfully. But what will actually happen? Will CL complain when I define class c and say that it invalidates the method chain for generic function m? Or will there be a runtime error?

Alexey
  • 1,354
  • 13
  • 30
  • I guess that you wanted the first `defmethod` to specialize its argument on `o`, right? – Svante Apr 30 '15 at 13:34
  • You should use `make-instance` instead of `allocate-instance`, and it takes a symbol, so you need to quote it when given literally. – Svante Apr 30 '15 at 13:36
  • I was writing my code using only the spec, so I could've made some errors. Thanks. – Alexey Apr 30 '15 at 13:51

2 Answers2

1

This should signal an error, according to the spec in Exceptional Situations:

When providing arguments to call-next-method, the following rule must be satisfied or an error of type error should be signaled: the ordered set of applicable methods for a changed set of arguments for call-next-method must be the same as the ordered set of applicable methods for the original arguments to the generic function. Optimizations of the error checking are possible, but they must not change the semantics of call-next-method.

As you correctly say, the topological class order will be:

  • c
  • a
  • b
  • o

Textual diagram:

   o
 /   \
a     b
 \   /
   c

As such, the list of applicable methods will be:

  • m (a)
  • m (b)
  • m (o)

So, if call-next-method doesn't signal an error, m (a) would pass an a to m (b), which is not a b.

This shouldn't happen, according to the spec, but I believe an implementation might choose to violate this rule for performance reasons. It's expensive to compute applicable methods on every call to call-next-method.

PS: In fact, depending on the implementation, call-next-method could check if the list of "top-level" matched specializers for the new arguments is the same as the original. To achieve this, the standard computed discriminating function must be more complex, and perhaps do some side work that non-standard compute-applicable-methods-using-classes and compute-applicable-methods might not do.

acelent
  • 7,965
  • 21
  • 39
  • For some reason, calling `(m aa)` resulted in "AOO" in SBCL, any idea why `m(o)` is invoked twice? – Alexey Apr 30 '15 at 19:52
  • It doesn't. It prints `"A"` in `m (a)`, then it prints `"O"` in `m (o)`, then the REPL prints the result, which is the argument to `print`, which is the string `"O"`. So, the two `"O"`s you see is a side-effect of the REPL. To prove this, try calling `(progn (m aa) (values))`, or adding `(values)` as the last form in `m (o)`. – acelent May 03 '15 at 21:03
1

It might fail at compile time. It will most likely fail at runtime.

The method m order for an object of class c is:

(c a b o)

It will hit a where the next method will be called on a new instance of a. a has this method order for method m:

(a o)

Since these have different method order call-next-method needs to signal an error. The part of CLHS that writes about this:

When providing arguments to call-next-method, the following rule must be satisfied or an error of type error should be signaled: the ordered set of applicable methods for a changed set of arguments for call-next-method must be the same as the ordered set of applicable methods for the original arguments to the generic function. Optimizations of the error checking are possible, but they must not change the semantics of call-next-method.

The fix would be not to add arguments to call-next-method. After that edit you can excpet this printed when calling (m cc):

"A" 
"B" 
"O" 

In CLISP it signals an error at runtime while SBCL don't, thus it actually works off spec in SBCL.

Sylwester
  • 47,942
  • 4
  • 47
  • 79
  • It shouldn't fail at compile time. It should fail at run time, but as you saw, some implementations don't follow the `call-next-method` rule when different arguments are specified, so in practice, it might not fail at run time. – acelent Apr 30 '15 at 15:39
  • @PauloMadeira It's not plausible that it will, but there isn't anything against it in the spec. Thats how I read *"Optimizations of the error checking are possible, but they must not change the semantics of call-next-method."*. – Sylwester Apr 30 '15 at 16:04
  • But that's regarding `call-next-method`'s own behavior, not the behavior of compiling calls to it. – acelent Apr 30 '15 at 17:55