-
Notifications
You must be signed in to change notification settings - Fork 34
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
Convenience props #433
base: main
Are you sure you want to change the base?
Convenience props #433
Conversation
aa50bab
to
fa861fe
Compare
Sorry, I missed this! I've been thinking about this too for elmer, whose classes have almost entirely scalar properties. I do think that this does belong in S7 because scalar values are such a common need and we don't currently offer any help. My model for thinking about this is based on the For this discussion, I think the most relevant are:
A few thoughts based on comparing our code to your properties:
Thanks for working on this and I look forward to discussing it more 😄 |
Thanks for your comments. They all seem reasonable to me. I don't feel that strongly about the details, as long as the convenience and code clarity remains. The only reason I messed around with default defaults is that they weren't hurting anything, but I agree it might be better to force the developer to be explicit. |
It would also be nice to allow for convenient
A sketch: new_setter <- function(set_once = FALSE, coerce = TRUE, nullable = TRUE) {
set_once; coerce
if (isTRUE(coerce)) {
coerce <- function(value) {
class <- eval(quote(S7_class(self)@properties[[name]]$class),
envir = parent.frame())
if(nullable)
for(class in class$classes)
if(!is.null(class))
break
convert(value, class)
}
} else if (isFALSE(coerce) || is.null(coerce)) {
coerce <- identity
} else {
stopifnot(is.function(coerce))
}
function(self, value) {
name <- attr(self, ".setting_prop", TRUE) |> tail(1) |> as.character()
if (set_once) {
if (!is.null(prop(self, name)))
stop(name, " can only be set once.")
}
if(nullable && is.null(value))
return(self)
prop(self, name) <- coerce(value)
self
}
}
prop_string <- function(default = NULL,
allow_null = FALSE,
allow_na = FALSE,
setter = NULL) {
force(allow_null)
force(allow_na)
new_property(
class = if (allow_null) NULL | class_character else class_character,
default = default,
setter = setter,
validator = function(value) {
if (allow_null && is.null(value)) {
return()
}
if (length(value) != 1) {
paste0("must be a single string, not '", as.character(value), "'.")
} else if (!allow_na && is.na(value)) {
"must not be missing."
}
}
)
} used like: Foo := new_class(
properties = list(
name = prop_string(
allow_null = FALSE,
default = quote(stop("name must be provided")),
setter = new_setter(set_once = TRUE, nullable = FALSE)
),
read_only = prop_bool(allow_null = TRUE,
setter = new_setter(set_once = TRUE))
)) |
Interesting ideas. We might consider those behaviors the domain of the property object itself, leaving the setter to be defined as normal. Any user setter would get wrapped with the extra logic. |
Some motivators for automatically calling
|
I'm sceptical that we want to automatically convert, because I think automatic coercions tend to hide problems that you want to know about. I think scalar property helpers will need something custom, because if you have an integer property, I think that 2 is an acceptable value but 2.5 should throw an error. |
@t-kalinowski here are the convenience property constructors that we discussed. They seem useful in my applications of S7; however, that does not necessarily mean that they belong in the base S7 package. Curious about everyone's thoughts.