r-lib / R6

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

Consider adding static methods/fields #66

Open wch opened 9 years ago

wch commented 9 years ago

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 commented 9 years ago

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.

gaborcsardi commented 9 years ago

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

wch commented 9 years ago

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().

fabiangehring commented 8 years ago

+1 on static methods

gaborcsardi commented 7 years ago

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.

richierocks commented 7 years ago

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.

wch commented 7 years ago

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:

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...

gaborcsardi commented 7 years ago

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".