Open wch opened 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.
@wch I don't think this is desired, I mean having to keep the generator function, with its environment, for every single object.
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()
.
+1 on static methods
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.
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.
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...
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".
If a generator is named
MyClass
, it would be nice to be able to callMyClass$foo()
. And inside of methods, you'd callclass$foo()
.