R6 icon indicating copy to clipboard operation
R6 copied to clipboard

Consider adding static methods/fields

Open wch opened this issue 9 years ago • 8 comments

If a generator is named MyClass, it would be nice to be able to call MyClass$foo(). And inside of methods, you'd call class$foo().

wch avatar Jun 24 '15 20:06 wch

One thing to consider is that if an R6 object keeps a reference to its generator, then the object brings along the generator when it's saved.

wch avatar Aug 07 '15 01:08 wch

@wch I don't think this is desired, I mean having to keep the generator function, with its environment, for every single object.

gaborcsardi avatar Aug 07 '15 11:08 gaborcsardi

Yeah, it might be simpler for someone to just create a new environment or list with some functions in it. You might have objects of class String, and then a separate list with some functions in it, like StringUtils. Then methods in String could just call StringUtils$foo().

wch avatar Aug 07 '15 15:08 wch

+1 on static methods

fabiangehring avatar May 26 '16 12:05 fabiangehring

How about just not keeping the static methods/variables for objects? If the user needs the static methods, they'll just use the generator. If an object needs the static method, they can refer to the generator explicitly.

The generator is not environment, so it does not have reference semantics currently. But maybe we can work around that somehow, e.g. sticking in a member that is an environment: generator$static <- new.env().

Currently I do use static constants, that belong to the class. I just stick them to the generator: generator$myconstant <- "foo" I guess I might as well put them in the objects, behind an active binding, or as a private member with a get method....

Just thinking aloud, really.

gaborcsardi avatar Sep 23 '16 19:09 gaborcsardi

Using environments to mimic static fields, as suggested by @gaborcsardi, seems like a good idea. It's useful to be able to distinguish public static fields and private static fields, as you would in Java or C#.

So in my opinion, there should be a static environment inside self, and another inside private. Then you can access these fields using self$static and private$static, which is fairly close to the Java/C# syntax, making it easier for people coming from a traditional OOP background to understand.

My current workaround is to define an environment named static inside public and/or private as needed.

library(R6)
rtc_factory <- R6Class(
  "ReferenceTestClass",
  public = list(
    static = new.env(),
    instance = list(),
    show = function() {
      message("Static fields:")
      print(capture.output(ls.str(self$static)))
      message("Instance fields:")
      print(capture.output(ls.str(self$instance)))
    }
  )
)

rtc_object1 <- rtc_factory$new()
rtc_object1$static$x <- 1
rtc_object1$instance$a <- 2

rtc_object2 <- rtc_factory$new()
rtc_object2$static$y <- 3
rtc_object2$instance$b <- 4

rtc_object1$show()
## Static fields:
## [1] "x :  num 1" "y :  num 3"
## Instance fields:
## [1] "a :  num 2"
rtc_object2$show()
## Static fields:
## [1] "x :  num 1" "y :  num 3"
## Instance fields:
## [1] "b :  num 4"

To make this feature easier to for users, I think it would be better if these static environments were created automatically by the R6Class function.

richierocks avatar Oct 04 '16 07:10 richierocks

For static members, here are some things that would need to be figured out. And then there's also the matter of making sure that it could actually be implemented.

(1) How are they accessed from outside of the class?

It would be nice to do Class$method() (where Class is an R6 generator object), but I think it's too late for that at this point, because the Class object already has many members which people may already be using. A more verbose alternative is Class$static$method(), which should be safe.

(2) How are they accessed from other static methods?

Some possibilities: method(), static$method(), or Class$static$method().

(3) How are they accessed from inside of an R6 object?

Some possibilities: method(), static$method(), self$static$method(), or Class$static$method().

(4) Should there be public and private static members?

I'd lean toward just having public static members, for simplicity.

(5) Should they be mutable? And should this be configurable?

I think the default is that the set of static items should be locked, the same as public and private.

(6) How should inheritance work?

Maybe don't have inheritance at all?


Things I want to avoid:

(1) Embedding an entire generator object in an instance object. The instances should not contain any references (direct or indirect) to the generator.

(2) Build-time/run-time compatibility problems. For example, suppose package A 1.0 has a class AC with AC$static$x=1, and you have a package B which creates and saves object a, where a <- AC$new(). Then you upgrade package A to 2.0, and it has AC$static$x=2 . At this point, if something in package B accesses AC$static$x, it seems clear that you should get 2. But if a method in object a accesses self$static$x, should it get 1 or 2? I think the answer is that you should get 2, but then self$static has to be dynamically bound. This may be possible by using an active binding. This build-time/run-time issue is a real one that I've run into several times in the past in various forms, and I've designed R6 to avoid it so far.


With your rct_factory example, it looks like the static field is mostly there to share values between instances of the class. I'm pretty sure that you could run into a problem which is closely related to the build/run time thing I described above.

Suppose package A has rtc_factory, and an instance, rtc_object_a. Then you install package B, which contains an instance, rtc_object_b. Depending on how those packages were built, the instances may not share the same static environment. I'm 100% sure that if this were the order, that static would not be shared:

  • Install package A 1.0
  • Install package B
  • Reinstall package A 1.0 (or upgrade to A 2.0)

If you're on a platform that installs from binary packages, there are even more places for things to go wrong.


I don't have the answer yet, but writing this has at least helped clarify in my mind what the problems are...

wch avatar Oct 27 '16 16:10 wch

Nice writeup!

After reading it, I have the feeling that this just does not fit into R6, and/or impossible to implement well.

This said, it would be nice to have a "suggested" way to share information between instances of a class. Although this could be as simple as "put it in an environment within the package namespace".

gaborcsardi avatar Oct 28 '16 08:10 gaborcsardi