r-lib / R6

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

reverse inheritance in R6 set method #160

Closed yonicd closed 5 years ago

yonicd commented 5 years ago

I am seeing a behavior of reverse inheritance that I don't know how to break.

If I define an R6 (Simple), then define a new object (obj) add a new field to obj via obj$set, then Simple is also manipulated.

(Not sure what the right title for the issue should be)

Simple <- R6::R6Class("Simple",
                      public = list(
                        x = 1,
                        getx = function() self$x
                      )
)

obj <- Simple

obj$set(which = 'public',name = 'new_field','aaa')

Simple
#> <Simple> object generator
#>   Public:
#>     x: 1
#>     new_field: aaa
#>     getx: function () 
#>     clone: function (deep = FALSE) 
#>   Parent env: <environment: R_GlobalEnv>
#>   Locked objects: TRUE
#>   Locked class: FALSE
#>   Portable: TRUE

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

yonicd commented 5 years ago

this is a solution, but kind of hacky

Simple <- function(){
  R6::R6Class("Simple",
                        public = list(
                          x = 1,
                          getx = function() self$x
                        )
  )
}

obj <- Simple()

obj$set(which = 'public',name = 'new_field','aaa')

obj
#> <Simple> object generator
#>   Public:
#>     x: 1
#>     new_field: aaa
#>     getx: function () 
#>     clone: function (deep = FALSE) 
#>   Parent env: <environment: 0x7fbeabe4f340>
#>   Locked objects: TRUE
#>   Locked class: FALSE
#>   Portable: TRUE

obj2 <- Simple()

obj2
#> <Simple> object generator
#>   Public:
#>     x: 1
#>     getx: function () 
#>     clone: function (deep = FALSE) 
#>   Parent env: <environment: 0x7fbeaceed2c8>
#>   Locked objects: TRUE
#>   Locked class: FALSE
#>   Portable: TRUE

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

wch commented 5 years ago

I wouldn't say that this is related to inheritance; the issue is that the generator object is an environment, so it has reference semantics.

is.environment(Simple)
#> [1] TRUE

That's why you can call Simple$set(), and have it alter the Simple object, instead of having to do something like Simple <- Simple$set().

I think your workaround makes sense here.

What is the use case for this?

yonicd commented 5 years ago

a good analogy would be purrr::partial.

I have an R6 object in a package that characterizes a trial.

In it there are public fields/methods that represent a broad range of parts that are included in a stage of a generic trial.

In reality though trial specific fields/methods need to be added to the R6 by the user, where new() is treated as a trial scenario.

This is where the set() function comes in. Since set() can only be called before new() and clone() is only available after new() then I can't effectively clone the R6 object.

Since the R6 is from the namespace of the package, once the set() is invoked it traverses back into the object in the package, which is a real problem because the next time in the session the user wants to use the package R6 object to create a new trial generator they have public fields/methods from a previous trial in it.

Could encapsulate be changed a bit to create new environments every time it is called? that way the same basic functionality of obj <- Simple; obj$set() that you described above would still be possible, but the basic generator of Simple would be unchanged ready for another clone.

wch commented 5 years ago

I have an R6 object in a package that characterizes a trial.

Do you mean you have an R6 class in a package?

Can you just make a subclass of it when you want to add trial-specific stuff, like this:

Simple2 <- R6Class("Simple2",
 inherit = Simple
)

Simple2$set( .... )

obj <- Simple2$new()

To me, that seems like the natural way to handle this kind of case.

Changing encapsulate would not help here; it does something different from what you're implying.

yonicd commented 5 years ago

I'll try that. thanks for the tip!