0

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.

mattek
  • 903
  • 1
  • 6
  • 18

1 Answers1

2

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
G. Grothendieck
  • 254,981
  • 17
  • 203
  • 341
  • This. "R is a functional language" should be emphasised – Hong Ooi Mar 19 '21 at 00:21
  • That’s very educational, thank you! Which of these options would be recommended from the performance/memory management point of view? In reality `data1` and `data2` would be potentially big `data.table`’s. Are S3 classes even appropriate for that? Should I opt for the S6 framework? – mattek Mar 19 '21 at 04:44
  • 1
    (1) is the most widely used (2) is consistent with it -- it only changes the method discussed but the data is still stored the same way so it is optional. I wouldn't try to guess on performance since that tends to result in surprises. Implement any approaches you are considering and then benchmark them. – G. Grothendieck Mar 19 '21 at 10:38