Search code examples
roperator-overloadingr-packager6

Arithmetic operators overload for R6classes in R


I am working on a custom R6Class which will be the main structure of my R package. But the problem is that this:

setMethod('+', signature("MyClass"), function(e1, e2)1)

or

setMethod('+', signature("R6"), function(e1, e2)1)

are not working because when I run

a = MyClass$new()
a + a

I get the following error Error in a + a : non-numeric argument to binary operator

The only way I got it working is like this

`+.MyClass` = function(e1, e2) { return (1) }

but this is not useful since when load the package from scratch (library(MyClass)) on an empty environment +.MyClass is not defined.

So, how can operators for R6 classes be overloaded the right way?

Just in case, the snippet:

MyClass = R6Class(
   "MyClass",
   public = list(
      initialize = function() {
      }
   ),
   private = list(
   )
)

setMethod('+', signature("MyClass"), function(e1, e2)1)
a = MyClass$new()
a+a

Solution

  • When you load the package, +.MyClass should absolutely be available, assuming you've exported the function.1

    I can demonstrate this with a minimal example, for which I used devtools -- I can't recommend enough the use of devtools for package creation. First, I create a minimal package structure:

    devtools::create("dummypack", rstudio = FALSE)
    

    Then I add a single R file in the "dummypack/R/" folder, "MyClass.R":

    #' @export
    MyClass = R6::R6Class(
        "MyClass",
        public = list(
            initialize = function() {
            }
        ),
        private = list(
        )
    )
    
    #' @export
    `+.MyClass` = function(e1, e2) { return (1) }
    

    The #' @export tags here are critical;2 what you need for this function to be available is that it is exported in the "dummypack/NAMESPACE" file (see the Namespaces chapter of Hadley Wickham's R packages). You need your NAMESPACE file to look like

    export(MyClass)
    export(`+.MyClass`)
    

    to make sure both the class and the overloaded + operator are exported. Alternatively you could have a NAMESPACE like

    exportPattern("^[^\\.]")
    

    to export everything that does not begin with a . (this is the NAMESPACE file initially generated by devtools::create()). So, if you are not using devtools (and Roxygen -- roxygen2), you need to edit the NAMESPACE file yourself to make sure your functions are exported.

    Then I run

    devtools::install("dummypack/")
    

    and in a fresh R session I run:

    library(dummypack)
    a = MyClass$new()
    a+a
    # [1] 1
    

    to show that you get the result you'd want.


    1 I would have thought this question could be easily marked as a duplicate. This question was the closest I found, but seemed to be a little too Roxygen-specific.

    2 Note that those tags only help with your NAMESPACE file if you are using Roxygen (which would work if you were using devtools::document()). Also, for the minimal example I show, the tags aren't technically necessary since the default NAMESPACE generated by devtools::create() just has the line exportPattern("^[^\\.]") as discussed above. However, in the typical case you want these tags and either way the important thing is what ends up in the NAMESPACE file -- you need explicit export() statements in NAMESPACE or a statement such as exportPattern("^[^\\.]").