Search code examples
roopr-s3

R: modifying property of an existing S3 object with a generic method


Let's say I have a constructor of a class with two properties with one initiated and the other set to NULL:

myclass <- function(data) {
  structure(
    list(data1 = data,
         data2 = NULL),
    class = "myclass")
}

And a generic:

myadd <- function(obj, x) {
  UseMethod("myadd")
}

myadd.myclass <- function(obj, x) {
  obj$data2 = obj$data1 + x
}

When I do:

mc = myclass(1)
myadd(mc, 2)

The property data2 does not change:

> mc
$data1
[1] 1

$data2
NULL

attr(,"class")
[1] "myclass"

Obviously, when I assign the result to a variable:

tmp = myadd(mc, 2)

I get the result:

> tmp
[1] 3

How to modify the property of an existing object with a generic function? Is it even kosher to do so?

I'm guessing I'm missing some crucial piece of info about S3 classes in R or about OOP in general. Any tips appreciated.


Solution

  • 1) pass it back R makes a copy of obj in the function when there is an attempt to modify it. The original obj is not changed. The idea is that R is a functional language and that minimizes side effects.

    Pass it back to the caller and assign it in the caller.

    myadd.myclass <- function(obj, x) {
      obj$data2 = obj$data1 + x
      obj
    }
    
    mc <- myclass(1)
    mc <- myadd(mc, 2)
    mc
    ## $data1
    ## [1] 1
    ##
    ## $data2
    ## [1] 3
    ##
    ## attr(,"class")
    ## [1] "myclass"
    

    2) replacement method Another possibility is to define a replacement function:

    "myadd<-" <- function(x, ..., value) UseMethod("myadd<-")
    "myadd<-.myclass" <- function(x, ..., value) { x$data2 <- x$data1 + value; x }
    
    mc <- myclass(1)
    myadd(mc) <- 2
    mc
    ## $data1
    ## [1] 1
    ##
    ## $data2
    ## [1] 3
    ##
    ## attr(,"class")
    ## [1] "myclass"
    

    3) environment Yet another approach is to use an environment rather than a list.

    myclass <- function(data) {
        structure(local({
          data1 = data
          data2 = NULL
          environment()
        }), class = c("myclass", "environment"))
    }
    
    # next two functions are same as in question
    myadd <- function(obj, x) UseMethod("myadd")
    myadd.myclass <- function(obj, x) obj$data2 = obj$data1 + x
    
    obj <- myclass(1)
    myadd(obj, 2)
    as.list(obj)
    ## $data1
    ## [1] 1
    ##
    ## $data2
    ## [1] 3