Detailed Behavior of UseMethod

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.
John Chambers<jmc@research.bell-labs.com>
Last modified: Thu Jun 25 15:06:46 EDT 1998