R6 icon indicating copy to clipboard operation
R6 copied to clipboard

Indexing methods

Open ifellows opened this issue 5 years ago • 9 comments

I recently wanted to implement indexing for some R6 classes and see that others are needing something similar. It doesn't look like there is an easy built in way to do this, but it looks like it could be added without too much effort.

`[.R6` <- function(x, ...) x$`[`(...) 
`[<-.R6` <- function(x, ...) x$`[<-`(...) 

Which allows

library(R6)

Foo = R6::R6Class(
    'Foo',
    public = list(
        X = NULL,
        metadata = NULL,
        initialize = function(X, metadata){
            self$X <- X
            self$metadata <- metadata
        },
        `[` = function(i, j){
            subfoo <- Foo$new(X = self$X[i, j, drop=FALSE], 
                             metadata = self$metadata)
            return(subfoo)
        }
    )
)

X <- matrix(1:8, ncol = 2)
foo <- Foo$new(X, 'blah blah')
foo[1:2,]

Is this something it makes sense to have in the package?

As an aside, overriding [[ wouldn't be good due to clashes with the existing environment dispatch. Is there any technical way to override ( so that objects could be functors?

ifellows avatar Aug 28 '18 21:08 ifellows

I can understand the desire to have a [ and [<- method for certain cases, but I feel that it's just kind of weird to make that a default for R6 classes in general.

I agree that overriding [[ is a bad idea because it would change existing behavior.

I don't think there's a way to override ( in R -- that is, only functions can use (. However, it is possible to create a function that has an object attached as an attribute, and override $ so that it accesses the object. For example:

library(R6)

# This takes an object with a $call() method
make_functor <- function(obj) {
  structure(
    function(...) {
      obj$call(...)
    },
    class = "functor",
    obj = obj
  )
}

`$.functor` <- function(x, name) {
  attr(x, "obj", exact = TRUE)[[name]]
}

`$<-.functor` <- function(x, name, value) {
  obj <- attr(x, "obj", exact = TRUE)
  obj[[name]] <- value
  # This function requires obj to be a reference object.
  # It could work with non-ref objects by adding `attr(x, "obj") <- obj` here.
  x
}

`[[.functor` <- `$.functor`
`[[<-.functor` <- `$<-.functor`



MyClass <- R6Class("MyClass",
  public = list(
    x = 1,
    call = function(n) {
      n + self$x
    }
  )
)


f <- make_functor(MyClass$new())
f(10)
#> [1] 11
f$x
#> [1] 1


f$x <- 2
f(10)
#> [1] 12
f$x
#> [1] 2

wch avatar Aug 30 '18 03:08 wch

Thanks for the reply. Good idea on the functor implementation.

I'd start by saying that this is "not a big deal," but let me make the case anyway.

  1. You agree that it is desirable for some R6 use cases to overload the indexing operator.
  2. There is no current way to overload the operator within the class system. The user is required to leave the class system and define an S3 method. This is what I would call "weird." Both S3 and S4 allow for [ and [[ overloading without leaving the class system.
  3. Since accessing an R6 object via x[ and x[<- will throw an error currently, there isn't much if any risk to supporting the operator overload. The only behavioral change would be to a class that implemented the x$[ method, and there shouldn't be any of those.
  4. Operator overloading is a very common feature object systems in languages other than R. I think that it is worth thinking carefully about how this could be supported in R6, especially if it is moving towards being a dominant object system in R. This would include thinking about %% operators.
  5. It just makes sense from a usability standpoint. If I want to implement [, I should create an [ R6 method.

ifellows avatar Aug 30 '18 06:08 ifellows

I would strongly argue against providing a [ method (unless that error just throws an error message). The [ method needs to be compatible with length() and names(), and also [[ and $. Defining a [ method that is not compatible is going to cause many weird problems.

Another way of saying this is that R6 provides scalar objects. If you want to provide vector objects, you need to use a different OO system.

hadley avatar Oct 08 '18 12:10 hadley

@hadley Why does [ need to be compatible with length(), names(), [[, and $? Is there code in base R that uses [ with these other functions?

wch avatar Oct 08 '18 18:10 wch

At least str() does.

The four methods are inextricably linked because length() tells you the valid set of integer values for use with [[, and names() tells you the valid set of character values. x[["y"]] should be equivalent to x$y, and has undefined behaviour if y is not in names(x)

hadley avatar Oct 08 '18 18:10 hadley

FWIW base R doesn't ensure that [[ is compatible with length() but I think that is a continuing source of confusion, not a reason to support it in R6.

x <- new.env()
x$a <- 1
x$b <- 2

length(x)
#> [1] 2
x[[1]]
#> Error in x[[1]]: wrong arguments for subsetting an environment

Created on 2018-10-09 by the reprex package (v0.2.1)

hadley avatar Oct 09 '18 13:10 hadley

It might now be possible to do this in a principled way, using what we've learned from writing vctrs.

hadley avatar Apr 03 '20 13:04 hadley

Is there any activity on this feature?

sebffischer avatar Nov 30 '21 08:11 sebffischer

@sebffischer if there was it, you'd see it in this issue.

hadley avatar Nov 30 '21 17:11 hadley