The old-style methods are initially invoked by a call to
UseMethod
.
What happens next is described in section A.6 of Statistical
Methods in S.
The description there is actually a ambiguous in the case that the
generic function modifies the first argument before calling
UseMethod
.
The statement on page 467 that the method acts as if the call to the
generic had been a call to the method is then in conflict with the
statement on the next page that the first argument will have been
evaluated.
What if it's not only evaluated but then modified?
On the whole, generic functions probably shouldn't do this very
often--the philosophy of generic functions is that they let the
methods do the computation.
I don't believe anyone brought this inconsistency to our attention for six years or so,
until Rob Gentleman pointed it out.
Implementing this model for method dispatch requires stepping outside
the standard function-call model.
In both the original and the current implementation the evaluator uses some special
techniques, but not quite the same technique.
As a result, the exact semantics vary a little.
The current semantics seem slightly more consistent, and in fact stick
somewhat more closely to the standard model.
One sentence, on page 467 in Statistical
Methods in S, is not literally true:
the frame for the call to the generic does not become the frame for
the method, but instead is replaced by a new frame, which has the same
parent frame and the same actual arguments (rematched if the method
has a different formal argument list) as the call to the generic.
The call to the generic remains open until the method returns, when
the frames for both calls will be popped.
The values of objects in the frame reflect any re-assignments done
before the call to UseMethod
.
The important part of the semantics remains true in both
implementations:
the parent frame of the method is the frame of the caller to the
generic function.
So use of functions such as substitute
in the method
works naturally.
As another aside, the comments on page 447 of Programming with
Data are probably too pessimistic in this respect.
Contrary to the assertion in point 1 on that page, most calls to
substitute
and sys.parent
should behave as
they did in the old days.
The following example illustrates the points made so far. It's to be hoped, though, that very few actual computations will end up depending on this level of detail.
> oldF function(x, y, ...) { x = x + 1; y = y + 1 UseMethod("oldF") } > oldF.foo function(x, y, z) { sum = x + y + z label = substitute(x) sum } > trace(oldF.foo,exit=browser) > Tx [1] 1 attr(, "class"): [1] "foo" > oldF(Tx + 1, 2, 100 + 1) On exit: Called from: oldF.foo(Tx + 1, 2, 100 + 1) b(oldF.foo)> x [1] 3 attr(, "class"): [1] "foo" b(oldF.foo)> y [1] 3 b(oldF.foo)> label Tx + 1 b(oldF.foo)> q [1] 107 attr(, "class"): [1] "foo" > oldClass(Tx) [1] "foo"In S-Plus 3.4, with the same example, x has the modified value, but y does not. I believe the reason is that all the arguments are re-matched and then x only (since it's known to have been evaluated) is copied into the new frame.