rgenericsmethodsr-s3

What is the purpose/effect of providing the 'generic' argument to `NextMethod()`?


I am trying to understand what the purpose of the generic argument of NextMethod() is.

Example

# example data
x <- as.Date(c("2022-01-01", "2023-01-01", "1900-01-01", "2024-01-01")) 

# gives cumulative maximum as expected
cummax.Date <- function(x, ...) .Date(NextMethod(), cl = oldClass(x))
cummax(x)
#> [1] "2022-01-01" "2023-01-01" "2023-01-01" "2024-01-01"

# gives cumulative minimum as expected
cummin.Date <- function(x, ...) .Date(NextMethod(), cl = oldClass(x))
cummin(x)
#> [1] "2022-01-01" "2022-01-01" "1900-01-01" "1900-01-01"

Best Practice

Quite often, the generic is called explicitely.

cummin.Date <- function(x, ...) .Date(NextMethod(generic = "cummin"), cl = oldClass(x))
cummin(x)
#> [1] "2022-01-01" "2022-01-01" "1900-01-01" "1900-01-01"

Bad Practice - no Difference

However, explicitly calling the generic does not impact the result whatsoever, no matter if it exists or not.

# still returns the cumulative minimum
cummin.Date <- function(x, ...) .Date(NextMethod(generic = "cummax"), cl = oldClass(x))
cummin(x)
#> [1] "2022-01-01" "2022-01-01" "1900-01-01" "1900-01-01"

# still returns the cumulative minimum
cummin.Date <- function(x, ...) .Date(NextMethod(generic = "foobar"), cl = oldClass(x))
cummin(x)
#> [1] "2022-01-01" "2022-01-01" "1900-01-01" "1900-01-01"

Solution

  • NextMethod uses argument generic only if it does not find the symbol .Generic in the calling environment of the method. That happens only in atypical usage:

    > .S3method("mean", "a", function(x, ...) { rm(.Generic); NextMethod() })
    > mean(structure(1:6, class = "a"))
    Error in NextMethod() : generic function not specified
    > .S3method("mean", "b", function(x, ...) { rm(.Generic); NextMethod("diff") })
    > mean(structure(1:6, class = "b"))
    [1] 1 1 1 1 1
    attr(,"class")
    [1] "b"
    > .S3method("mean", "c", function(x, ...) { .Generic <- "diff"; NextMethod() })
    > mean(structure(1:6, class = "c"))
    [1] 1 1 1 1 1
    attr(,"class")
    [1] "c"
    

    It is undocumented (I think), but visible in the sources of the internal do_nextmethod, which uses readS3VarsFromFrame (defined elsewhere) to locate .Generic.

    I don't think that anyone would recommend assigning to .Generic, removing .Generic, or otherwise calling NextMethod in a context where .Generic is not there. Indeed, I think that the advice in help("NextMethod") about .Class applies here:

    Note that .Class is set when the generic is called, and is unchanged if the class of the dispatching argument is changed in a method. It is possible to change the method that NextMethod would dispatch by manipulating .Class, but 'this is not recommended unless you understand the inheritance mechanism thoroughly' (Chambers & Hastie, 1992, p. 469).