roopgenerics

Using NextMethod with internal generics


With a generic that uses UseMethod, NextMethod works as I expect:

f <- function(...) UseMethod("f")
f.default <- function(...) 5
f.foo <- function(...) {print("hi"); NextMethod()}
x <- structure(1, class = "foo")
f(x) # prints "hi", returns 5

With the following internal generic, a similar approach does not work:

# NB: base::cbind is an internal generic
cbind.default <- function(...) 5
cbind.foo <- function(...) {print("hi"); NextMethod()}
cbind(x) # prints "hi", Error in NextMethod() : generic function not specified

Is NextMethod usable with internal generics, and if so, what arguments etc. are needed?


Solution

  • This behaviour stems from bespoke machinery for dispatch on ... in the C-level do_bind (see bind.c). It does not use the conventional mechanism for dispatch documented in help("NextMethod") and implemented in the C-level Dispatch[Any]OrEval() (see eval.c). Crucially, method look-up succeeds but the method call is not evaluated in an environment defining the variables .Generic, .Method, .Class, etc. essential for dispatch by NextMethod.

    On one hand, it is well documented that the dispatch mechanism used by [cr]bind is exceptional (see section "Dispatch" in help("cbind")). On the other hand, c is conceptually quite parallel to [cr]bind, also dispatching internally on ..., but c does not have this limitation:

    > m <- function(...) { cat("a\n"); NextMethod() }
    > for (generic in c("c", "cbind", "rbind")) .S3method(generic, "a", m)
    > x <- structure(0, class = "a")
    > c(x)
    a
    [1] 0
    > cbind(x)
    a
    Error in NextMethod() : generic function not specified
    > rbind(x)
    a
    Error in NextMethod() : generic function not specified
    > debug(m)
    > c(x)
    debugging in: c.a(x)
    debug at #1: {
        cat("a\n")
        NextMethod()
    }
    Browse[1]> ls(all.names = TRUE)
    [1] "..."             ".Class"          ".Generic"        ".GenericCallEnv"
    [5] ".GenericDefEnv"  ".Group"          ".Method"        
    Browse[1]> Q
    > cbind(x)
    debugging in: cbind(deparse.level, ...)
    debug at #1: {
        cat("a\n")
        NextMethod()
    }
    Browse[1]> ls(all.names = TRUE)
    [1] "..."
    Browse[1]> Q
    > rbind(x)
    debugging in: rbind(deparse.level, ...)
    debug at #1: {
        cat("a\n")
        NextMethod()
    }
    Browse[1]> ls(all.names = TRUE)
    [1] "..."
    Browse[1]> Q
    

    It seems quite "wrong" to me that [cr]bind and c should behave differently here. So I've filed a bug report: https://bugs.r-project.org/show_bug.cgi?id=18779