-
Notifications
You must be signed in to change notification settings - Fork 37
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
Allow read-only properties to be set in the constructor #404
Comments
I think you could do this by having a setter that throws an error if the Date is already set (not |
That's a good idea, but I can't quite get it to work because there are some barriers in the way, including the issue I mentioned in #405 where you can't use default values and a constructor in tandem.
Our setup now looks like this: person <- S7::new_class(
name = 'person',
properties = list(
'name' = S7::class_character,
'birthdate' = S7::new_property(
class = S7::class_Date,
getter = \(self) { self@birthdate },
setter = \(self, value) {
if (is.na(self@birthdate)) self@birthdate <- value else warning('Cannot set read-only value')
return(self)
},
),
'age' = S7::new_property(
class = S7::class_any,
getter = \(self) { Sys.Date() - self@birthdate }
)
),
constructor = \(name, birthdate = as.Date(NA)) {
S7::new_object(
S7::S7_object(),
'name' = name,
'birthdate' = birthdate
)
}
)
person('Unixtime') If you run this, you'll get a C stack error. I'm not really sure why this is. |
But the value of However, I was surprised to learn that property access does not behave as I would expect. The getter is run if and only if the underlying attribute value is Anyway, this is an approach you can take: Person <- S7::new_class(
name = 'Person',
properties = list(
'name' = S7::class_character,
'birthdate' = S7::new_property(
class = S7::class_Date,
setter = \(self, value) {
if (is.null(self@birthdate))
self@birthdate <- value
else warning('Cannot set read-only value')
return(self)
},
),
'age' = S7::new_property(
class = S7::class_any,
getter = \(self) { Sys.Date() - self@birthdate }
)
),
constructor = \(name, birthdate = as.Date(NA)) {
S7::new_object(
S7::S7_object(),
'name' = name,
'birthdate' = birthdate
)
}
)
Person("foo") No getter is needed on |
Having 'set-once' properties seems like a relatively common use case, and this requires a lot of boilerplate code. I wonder if there's a way we could support this without requiring users to manually specify the constructor. Two approaches come to mind:
new_property(..., getter = `@`, settable_once = TRUE)
new_property(..., getter = `@`, setter = "on_init_only") Neither strikes me as particularly elegant. Any other ideas? |
What if we used After reviewing the "Classes and Objects" vignette, I find it helpful to frame properties in terms of dynamic vs. non-dynamic, and settable vs. read-only:
Based on this framework:
Here's a summary of the property types we'd support:
|
I've thought much less than you about this.. still it makes much sense to think of such a 2 x 2 table of possibilities Needing clarification: in your list, you use |
Thanks, @mmaechler. I've edited my previous comment to restore the table and slightly edited the code examples to omit unnecessary args to In this proposal, supplying This opens the question of how a user could specify a property that does not have a default value in the constructor (asked in #376). Also, there's the related question of how to specify a default that is a promise that gets evaluated at construction time, like These might be addressed by supplying a language object to new_property(setter = FALSE, default = quote(expr =))
new_property(setter = FALSE, default = quote(Sys.time())) |
...
Yes, that would be a nice and "R-like" (though advanced R) way, indeed! |
|
Meeting notes, in agreement this is a good idea: # Current behavior, not included in constructor because getter is a function
new_property(getter = <function>, setter = <function>)
## proposal for new behavior:
# if there is a setter(), just always include it in the constructor
new_property(getter = <function>, setter = <function>) |
Closing, as we’ve reached a workable state for the next release. In S7, you can create a read-only property by throwing an error from a custom setter. For example: setter = function(self, value) {
if (is.null(self@birthdate)) {
self@birthdate <- value
self
} else {
stop('@birthdate can only be set once')
}
} See #449 for a summary of the discussion on this and related issues. |
Currently the documentation suggests that read-only properties (those with only a getter and no setter) are for computed values such as
now
or calculating using other properties of the object. However, sometimes you want to create an object with a value that doesn't change, isn't allowed to change, and isn't calculated from other values.Take this person class, for example. We want to allow the birthdate to be set once and only once on construction, but the person's age will be calculated using the current date.
The text was updated successfully, but these errors were encountered: