r-lib / R6

Encapsulated object-oriented programming for R
https://R6.r-lib.org
Other
404 stars 56 forks source link

Thoughts on making R6 classes iterable? #143

Closed zachary-foster closed 4 years ago

zachary-foster commented 6 years ago

I am using R6 in a package and some of our classes are basically just lists of other classes. Currently, we are using S3 for the classes that are lists of R6 objects. This works ok, but it would be nice if we could R6 for these classes as well. The only reason we don't is that the list classes can then not be used directly with lapply and such.

In python, there are iterators:

class Counter:
    def __init__(self, low, high):
        self.current = low
        self.high = high

    def __iter__(self):
        return self

    def next(self): # Python 3: def __next__(self)
        if self.current > self.high:
            raise StopIteration
        else:
            self.current += 1
            return self.current - 1

for c in Counter(3, 8):
    print c

I expect that this will not be practical in R due to how the *apply functions work.

Actually, looking at the lapply code:

> lapply
function (X, FUN, ...) 
{
    FUN <- match.fun(FUN)
    if (!is.vector(X) || is.object(X)) 
        X <- as.list(X)
    .Internal(lapply(X, FUN))
}
<bytecode: 0x33df9d8>
<environment: namespace:base>

The as.list could be used to supply something to iterate over if an S3 as.list.my_r6_class or as.list.R6 was added like:

library(R6)
Person <- R6Class("Person",
                  public = list(
                    name = NULL,
                    hair = NULL,
                    initialize = function(name = NA, hair = NA) {
                      self$name <- name
                      self$hair <- hair
                      self$greet()
                    },
                    set_hair = function(val) {
                      self$hair <- val
                    },
                    greet = function() {
                      cat(paste0("Hello, my name is ", self$name, ".\n"))
                    }
                  )
)

People <- R6Class("People",
                  public = list(
                    values = list(),
                    initialize = function(persons) {
                      self$values <- persons
                    },
                    to_list = function() {
                      return(self$values)
                    }
                  )
)

as.list.R6 <- function(x, ...) {
  if ("to_list" %in% names(x)) {
    return(x$to_list())
  } else {
    stop("Cannot convert R6 class to list...")
  }
}

joe = Person$new("joe", "shaggy")
mary = Person$new("mary", "neat")
bob = Person$new("bob", "bald")
candice = Person$new("candice", "red")

group = People$new(list(joe, mary, bob, candice))

Then lapply works:

> lapply(group, function(x) x$greet())
Hello, my name is joe.
Hello, my name is mary.
Hello, my name is bob.
Hello, my name is candice.
[[1]]
NULL

[[2]]
NULL

[[3]]
NULL

[[4]]
NULL

However, for loops do not work:

> for (x in group) {
+   x$greet()
+ }
Error in for (x in group) { : invalid for() loop sequence

I might try this with my package, but it might be useful if this behavior was part of R6.The same thing could be done for apply if dims.R6 and as.matrix.R6 were defined. Just a thought; I am not sure what other implications adding those S3s would have.

hadley commented 5 years ago

I think this is a bad idea for the same reasons as #153 (and this is really a duplicate issue)