rnamespacesoperatorsinfix-operatorr-environment

Attaching infix operators


Benefit of attaching objects (you can skip this)

In a sourced script, it can be convenient to define our helper funcs like this:

f <- function() print('hi')
e <- new.env()
assign("f", f, e)
rm(f)
attach(e)
rm(e)

Now, f is kind of protected. It does not clutter your global environment:

ls()
#> character(0)  # if anything, no "f"
rm(f)
#> In rm(f) : object 'f' not found

But still e is on the search path:

search()
#> [1] ".GlobalEnv" "e" ... etc 

So:

f()  
#> [1] "hi"

Also, if we define another f in the global env, it takes precedence, but the attached function is protected from being deletes:

f <- function() print('hello')
f()  
#> [1] "hello"
rm(f)
f()  
#> [1] "hi" # again

To actually remove f remove (detach) its container:

detach(e)
f()
#> Error in f() : could not find function "f"

Attaching operators

Create a trivial infix operator

Consider the following operator definition:

`%plus%` <- function(x,y) UseMethod("%plus%")
`%plus%.numeric` <- function(x, y) x + y
1 %plus% 1
#> 2

Of course, that's for the sake of simplicity, but one can define a sophisticated %+% doing hard maths.

The issue

Let's try to attach our %plus% operator:

e <- new.env()
assign("%plus%.numeric", `%plus%.numeric`, e)
assign("%plus%", `%plus%`, e)
rm(`%plus%.numeric`)
rm(`%plus%`)
attach(e)
1 %plus% 1

gives:

Error in UseMethod("%plus%") : 
  no applicable method for '%plus%' applied to an object of class "c('double', 'numeric')"

Interestingly, the operator is visible:

`%plus%`
#> function(x,y) UseMethod("%plus%")
#> <bytecode: 0x557b338c86c0>

`%plus%.numeric`
#> function(x, y) x + y

And using the functional, rather than infix, form does work:

`%plus%.numeric`(1, 1)
#> 2

Solution

  • As user2554330 already points out in the comments, the issue arise not because of the infix operator but because of the method. You need to "register" the S3 method or it needs to be available in the environment of the call to the generic.

    The documentation of UseMethod elaborates on this:

    Namespaces can register methods for generic functions. To support this, UseMethod and NextMethod search for methods in two places: in the environment in which the generic function is called, and in the registration data base for the environment in which the generic is defined (typically a namespace). So methods for a generic function need to be available in the environment of the call to the generic, or they must be registered. (It does not matter whether they are visible in the environment in which the generic is defined.)

    `%plus%` <- function(x,y) UseMethod("%plus%")
    `%plus%.numeric` <- function(x, y) x + y
    
    e <- new.env()
    assign("%plus%.numeric", `%plus%.numeric`, e)
    assign("%plus%", `%plus%`, e)
    
    # this line is new
    registerS3method("%plus%","numeric","%plus%.numeric", envir = e)
    
    rm(`%plus%.numeric`)
    rm(`%plus%`)
    attach(e)
    
    1 %plus% 1
    #> [1] 2
    

    Created on 2024-01-28 with reprex v2.0.2