rmethodsr-s3search-pathmethod-dispatch

How does R find S3 methods? Why can't R find my S3 `+` method?


I would like to create and attach an environment containing S3 methods and have R find them in the search path, by using the method name (i.e., I should be able to get infix-style a + b to work and not have to write prefix-style "+.Foo"(a, b)). What am I doing wrong? Although rather longish, the shortest example that I could come up within a reasonable amount of time is below. The comments elaborate on the the situation and problem.

Update: I have appended some more code per the selected answer.

# **** CLEAR THE USER WORKSPACE / GLOBAL ENVIRONMENT ****
rm(list = ls( all = TRUE ))

# **** RESET THE SEARCH PATH. (I'VE NEVER HAD AN R INSTALLATION THAT DOESN'T BOOT ****
# **** WITH package:stats AS THE SECOND-TO-LAST ITEM ON THE SEARCH PATH.)                   ****
while (search()[[2]] != "package:stats") detach()

search ()
# [1] ".GlobalEnv" "package:stats" "package:graphics" "package:grDevices" "package:utils"
# [6] "package:datasets" "package:methods" "Autoloads" "package:base"     

x <- 1L
class (x) <- "Foo"

`+.Foo` <- function ( x , y ) {
    cat("This is `x.Foo`\n")
    # "non-standard" addition on purpose
    z <- -1L * ( as.integer(x) + as.integer(y) )
    class(z) <- "Foo"
    z }

x + x
# This is `x.Foo`
# [1] -2
# attr(,"class")
# [1] "Foo"

# Note that R finds `x.Foo` in the global environment.

FooEnv <- new.env(parent = as.environment(search()[[2]]))
attr(FooEnv, "name") <- "FooEnv"
FooEnv
# <environment: 0xhhhhhhhhhhhhhhhh> ## Each h is a hex-digit.
# attr(,"name")
# [1] "FooEnv"

parent.env(FooEnv)
# <environment: package:stats>
# attr(,"name")
# [1] "package:stats"
# attr(,"path")
# [1] "C:/Program Files/R/R-4.0.3/library/stats"

# The path above will vary with the R installation.

# I want the next 3 lines of R code is to replace `+.Foo` in the global environment
# with `x.Foo` on the search path.
FooEnv $ `+.Foo` <- `+.Foo`
rm(`+.Foo`)
attach(what = FooEnv, name = attr(FooEnv, "name"))

search()
# [1] ".GlobalEnv" "FooEnv" "package:stats" "package:graphics" "package:grDevices"
# [6] "package:utils" "package:datasets" "package:methods" "Autoloads" "package:base"    

x + x
# [1] 2
# attr(,"class")
# [1] "Foo"

## Note lack of "This is `x.Foo`".
## Note the sign.
## This is not the `+` we're looking for.

# `methods` finds `+.Foo`:
methods("+")
# [1] +.Date +.Foo +.POSIXt
# see '?methods' for accessing help and source code

# `find` finds `+.Foo`
find("+.Foo")
# [1] "FooEnv"

# Although the following works, I want to call `x.Foo` using infix style.
`+.Foo`(x, x)
# This is `x.Foo`
# [1] -2
# attr(,"class")
# [1] "Foo"

write.dcf(R.Version())
# platform: x86_64-w64-mingw32
# arch: x86_64
# os: mingw32
# system: x86_64, mingw32
# status:
# major: 4
# minor: 0.3
# year: 2020
# month: 10
# day: 10
# svn rev: 79318
# language: R
# version.string: R version 4.0.3 (2020-10-10)
# nickname: Bunny-Wunnies Freak Out

# `.S3method` seems to do the trick. Acknowledgement: @MrFlick
.S3method("+", "Foo")
x + x
# This is `x.Foo`
# [1] -2
# attr(,"class")
# [1] "Foo"

Solution

  • If you aren't registering an S3 method as part of a package namespace or in the global environment, you'll need to explicitly register it using the .S3method() function. So in this case you would do

    .S3method("+", "Foo", FooEnv$`+.Foo`)
    

    This issue is further discussed here: https://developer.r-project.org/Blog/public/2019/08/19/s3-method-lookup/