The inner workings of `NextMethod()`

2.8k views Asked by At

I'm trying to figure out how NextMethod() works. The most detailed explanation I have found of the S3 class system is in Chambers & Hastie (edts.)'s Statistical Models in S (1993, Chapman & Hall), however I find the part concerning NextMethod invocation a little obscure. Following are the relevant paragraphs I'm trying to make sense of (pp. 268-269).

Turning now to methods invoked as a result of a call to NextMethod(), these behave as if they had been called from the previous method with a special call. The arguments in the call to the inherited method are the same in number, order, and actual argument names as those in the call to the current method (and, therefore, in the call to the generic). The expressions for the arguments, however, are the names of the corresponding formal arguments of the current method. Suppose, for example, that the expression print(ratings) has invoked the method print.ordered(). When this method invokes NextMethod(), this is equivalent to a call to print.factor() of the form print.factor(x), where x is here the x in the frame of print.ordered(). If several arguments match the formal argument "...", those arguments are represented in the call to the inherited method y special names "..1", "..2", etc. The evaluator recognizes these names and treats them appropriately (see page 476 for an example).

This rather subtle definition exists to ensure that the semantics of function calls in S carry over as cleanly as possible to the use of methods (compare Becker, Chambers and Wilks's The New S Language, page 354). In particular:

  • Arguments are passed down from the current method to the inherited method with their current values at the time NextMethod() is called.
  • Lazy evaluation continues in effect; unevaluated arguments stay unevaluated.
  • Missing arguments remain missing in the inherited method.
  • Arguments passed through the "..." formal argument arrive with the correct argument name.
  • Objects in the frame that do not correspond to actual arguments in the call will not be passed to the inherited method."

The inheritance process is essentially transparent so far as the arguments go.

Two points that I find confusing are:

  1. What is "the current method" and what is "the previous method"?
  2. What is the difference between "The arguments in the call to the inherited method", "The expressions for the arguments" and "the names of the corresponding formal arguments of the current method"?

Generally speaking, if anyone could please restate the description given in the above paragraphs in a lucider fashion, I'd appreciate it.

2

There are 2 answers

0
agstudy On BEST ANSWER

Hard to go through all this post, but I think that this small example can help to demystify the NextMethod dispatching.

I create an object with 2 classes attributes (inheritance) 'first' and 'second'.

x <- 1
attr(x,'class') <- c('first','second')

Then I create a generic method Cat to print my object

Cate <- function(x,...)UseMethod('Cate')

I define Cate method for each class.

Cate.first <- function(x,...){
  print(match.call())
  print(paste('first:',x))
  print('---------------------')
  NextMethod()                ## This will call Cate.second
}

Cate.second <- function(x,y){
  print(match.call())
  print(paste('second:',x,y))
}

Now you can can check Cate call using this example:

 Cate(x,1:3)
Cate.first(x = x, 1:3)
[1] "first: 1"
[1] "---------------------"
Cate.second(x = x, y = 1:3)  
[1] "second: 1 1" "second: 1 2" "second: 1 3"
  • For Cate.second the previous method is Cate.first
  • Arguments x and y are passed down from the current method to the inherited method with their current values at the time NextMethod() is called.
  • Argument y passed through the "..." formal argument arrive with the correct argument name Cate.second(x = x, y = 1:3)
0
G. Grothendieck On

Consider this example where generic function f is called and it invokes f.ordered and then, using NextMethod, f.ordered invokes f.factor:

f <- function(x) UseMethod("f")  # generic
f.ordered <- function(x) { x <- x[-1]; NextMethod() }
f.factor <- function(x) x # inherited method
x <- ordered(c("a", "b", "c"))

class(x)
## [1] "ordered" "factor" 

f(x)
## [1] b c
## Levels: a < b < c

Now consider the original text:

Turning now to methods invoked as a result of a call to NextMethod(), these behave as if they had been called from the previous method with a special call.

Here f calls f.ordered which calls f.factor so the method "invoked as a result of a call to NextMethod" is f.factor and the previous method is f.ordered.

The arguments in the call to the inherited method are the same in number, order, and actual argument names as those in the call to the current method (and, therefore, in the call to the generic). The expressions for the arguments, however, are the names of the corresponding formal arguments of the current method. Suppose, for example, that the expression print(ratings) has invoked the method print.ordered(). When this method invokes NextMethod(), this is equivalent to a call to print.factor() of the form print.factor(x), where x is here the x in the frame of print.ordered()

Now we switch perspectives and we are sitting in f.ordered so now f.ordered is the current method and f.factor is the inherited method.

At the point that f.ordered invokes NextMethod() a special call is constructed to call f.factor whose arguments are the same as those passed to f.ordered and to the generic f except that they refer to the versions of the arguments in f.ordered (which makes a difference here as f.ordered changes the argument before invoking f.factor.