-
Notifications
You must be signed in to change notification settings - Fork 57
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[question] How to enforce type of the fields in an R6 class #48
Comments
I think you need to check the arguments of the constructor if you want this, e.g. ...
initialize = function(name, age) {
if (!missing(name)) {
stopifnot(is.character(name), length(name) == 1)
self$name <- name
}
if (!missing(age)) {
stopifnot(is.numeric(age), length(age) == 1)
self$age <- age
}
self$greet()
},
... |
Yes, @gaborcsardi's solution should work for you. Alternately, you could use an active binding, if you want to enforce that the field always has the correct type, but there is a performance penalty for this. (I believe if you use Reference Classes, this is how it operates internally.) In some of the examples, I do have code like this: Person <- R6Class("Person",
public = list(
name = character(),
... but the |
OK, so there is no way to have the classes enforce the type of the fields. But then again, part of the reason to move from S4 to R6 is that I would like the object creation to be faster, so I don't want to incur too many performance penalties. |
Follow up question, would you be willing to add this type enforcement to fields in the future? Or is it too much effort, or you want them to stay light-weight? |
Have in mind that this even isn't really possible with the S4 system - the way it is currently done is that the class developer makes a validity function that checks if the object conforms. The developer is then responsible for calling .validObject within all setter functions (anything can be assigned directly with the @ accessor and no checks are made). This could easily be replicated in R6 using active bindings as stated above, and does not require any additions to the current implementation of R6. |
Come to think of it, it could be possible to have this done automagically. In the private definition you could have something like: # (truncated)
private = list(
number=enforce('numeric', 10),
string=enforce('character', set=FALSE),
normalField=1:10
)
# (truncated) The generator function could then look for fields that had been defined with the enforce function and create the relevant active bindings automatically. The enforce function would take the name of the accepted class, an optionally initial vale and a boolean indicating whether it should be possible to set the value. @wch If this is something that sound good to you, I can make a PR for it. I'm aware that you're trying to keep R6 minimal and efficient so it is up to you... |
@thomasp85 something like that would be quite nice. |
@wligtenberg - that's the idea. It could put a little overhead on object construction because some additional checks are needed, but I believe these will be minor compared to a hard coded class definition - possibly all of this can be moved to the constructor construction, so that the end result would be a constructor with equal performance as a hard coded equivalent (one defining the active bindings directly)... |
This is a good idea, but out of scope for R6, which is deliberately minimal. But I don't think there's anything holding you back from implementing in another package as a wrapper around R6. |
I have done something to the same effect, but a little different, and it works very well. Build an R6 'Checker' class like the following, here below I only include one such check, but in my class, there is a whole stack of theme probably 20 or 30 different types of checks that I use frequently, check of type, or range, or valid values, etc....
Basically, the checker class contains a library of regular checks that you use repetitively and frequently within implementing classes. Then in the actual class, where you want to enforce something, you can do it like this (it is also possible to inherit from the checker):
Build a wrapper function to make documentation easy, and construction convenient:
Now Test:
I'm thinking of making a rather trivial package with the checker class only, as I keep using it over and over and over again for multiple projects. |
@thomasp85 , do you think you could fork R6 and put in the implantation you suggest? Contrary to @hadley comments, this would fit in perfectly into a class system. Establishing a syntax like What's nice about yours is that it puts the enforcement logic very close to where the field is defined vs somewhere else where the field is assigned. When you get beyond some of the trivial examples of more cplicated objects, you really appreciate that you can see the constraint on the field type directly where the field is being defined. In fact, it's making enforcing a type an integral part of defining the field itself, which is really attractive. |
I'm afraid I don't have the development bandwidth for this right now |
I would like to use R6 classes for my project. One of the things I like about object oriented programming, is that you can enforce objects to have certain fields of specific types.
So that you can make sure that objects that are created are valid.
I tried the following, but that doesn't seem to work:
library(R6)
As you can see, I can still provide a character string as an argument to create the new instance.
If it is possible to restrict field to a certain class/type, could you please provide an example?
The text was updated successfully, but these errors were encountered: