-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Remove ellipsis from scale constructors #1763
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
Conversation
@Haldey - I just changed it for scale_alpha for the time being. Can you have a look and see if this approach looks good to you... |
I really don't like this approach, but I don't think there's any way to do better, so I think I'd rather fix this in roxygen2, by providing someway to specify that the docs for ... should come from That won't help with autocomplete though. Maybe @kevinushey will have some ideas. |
Which part is it you don't like? The forwarding of arguments or the doccumentation? |
The forwarding of the arguments. |
I can do it manually as is done for |
RStudio does not yet have the smart control-flow analysis needed to detect how However, it's possible we could also look at the function documentation for available arguments, e.g. by parsing the |
@@ -19,17 +17,34 @@ | |||
#' (p <- ggplot(mtcars, aes(mpg, cyl)) + | |||
#' geom_point(aes(alpha = factor(cyl)))) | |||
#' p + scale_alpha_discrete(range = c(0.4, 0.8)) | |||
scale_alpha <- function(..., range = c(0.1, 1)) { | |||
continuous_scale("alpha", "alpha_c", rescale_pal(range), ...) | |||
scale_alpha <- function(name = waiver(), range = c(0.1, 1), breaks = waiver(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if we wrote forward_to
so that this would work:
scale_alpha <- forward_to(continuous_scale,
aesthetics = "alpha",
scale_name = "alpha_c",
palette = rescale_pal()
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One downside is that this would require continuous_scale
to be defined before scale_alpha
in order to inspect the arguments.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or what about:
forward_to(contiunous_scale,
aesthetics = "alpha",
scale_name = "alpha_c",
palette = rescale_pal(range),
range = NULL
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
forward_to <- function(fun, ...) {
call <- as.call(c(quote(fun), find_args(...)))
eval(call)
}
I've made this piece of magical code that I would like you to consider (it depends on the forward_to <- function(.fun, ..., .mask, .prepare) {
args <- as.list(match.call())[-1]
args <- args[!names(args) %in% c(".fun", ".mask", ".prepare")]
frml <- formals(.fun)
.prepare <- substitute(.prepare)
.fun <- substitute(.fun)
new_frml <- modifyList(frml[!names(frml) %in% .mask], args)
new_fun <- function() {
args <- find_args()
prepared <- new.env()
eval(.prepare, envir = prepared)
args <- modifyList(args, as.list(prepared))
args <- args[names(args) %in% names(frml)]
call <- as.call(c(.fun, args))
eval(call)
}
formals(new_fun) <- new_frml
new_fun
} with it you can do: scale_alpha <- forward_to(continuous_scale,
range = c(0.1, 1),
.mask = c("palette", "super", "position", "rescaler", "aesthetics", "scale_name"),
.prepare = {
aesthetics <- "alpha"
scale_name <- "alpha_c"
palette <- rescale_pal(range)
}
) It's a bit verbose but it allow you to set new arguments with defaults, define which arguments in the destination function should be inaccessible and give freedom to manipulate and create variables prior to calling the destination function (any variable in |
Wouldn't the following be a simpler implementation and produce more readable function bodies, maybe I am missing a subtlety though... forward_to <- function(f, ..., .ignore = character(), .code = NULL) {
fmls <- modifyList(formals(f), list(...))
fmls <- fmls[!names(fmls) %in% .ignore]
called_fmls <- stats::setNames(lapply(names(fmls), as.symbol), names(fmls))
call <- as.call(c(substitute(f), called_fmls))
formals(f) <- fmls
body(f) <- bquote({
.(code)
.(call)},
as.environment(list(call = call, code = substitute(.code))))
f
}
forward_to(continuous_scale, range = c(0.1, 1),
.ignore = c("palette", "super", "position", "rescaler", "aesthetics", "scale_name"),
.code = {
aesthetics <- "alpha"
scale_name <- "alpha_c"
pallette <- rescale_pal(range)
})
#> function (name = waiver(), breaks = waiver(), minor_breaks = waiver(),
#> labels = waiver(), limits = NULL, oob = censor, expand = waiver(),
#> na.value = NA_real_, trans = "identity", guide = "legend",
#> range = c(0.1, 1))
#> {
#> {
#> aesthetics <- "alpha"
#> scale_name <- "alpha_c"
#> pallette <- rescale_pal(range)
#> }
#> continuous_scale(name = name, breaks = breaks, minor_breaks = minor_breaks,
#> labels = labels, limits = limits, oob = oob, expand = expand,
#> na.value = na.value, trans = trans, guide = guide, range = range)
#> }
#> <environment: namespace:ggplot2> |
I'm so new to meta-programming and was so proud until you came along @jimhester :-) But one thing your function is missing is to take matching variables from |
Ok this should work better, looks like all the arguments are being passed properly. forward_to <- function(f, ..., .dont_forward = character(), .dont_call = .dont_forward, .code = NULL) {
.code <- substitute(.code)
fmls <- formals(f)
forwarded_fmls <- fmls[!names(fmls) %in% .dont_forward]
called_fmls <- fmls[!names(fmls) %in% .dont_call]
forwarded_fmls <- modifyList(forwarded_fmls, list(...))
called_fmls <- stats::setNames(lapply(names(called_fmls), as.symbol), names(called_fmls))
call <- as.call(c(substitute(f), called_fmls))
formals(f) <- forwarded_fmls
body(f) <- bquote({
.(code)
.(call)},
as.environment(list(call = call, code = .code)))
f
}
forward_to(continuous_scale, range = c(0.1, 1),
.dont_forward = c("palette", "super", "position", "rescaler", "aesthetics", "scale_name"),
.dont_call = c("super", "position", "rescaler"),
.code = {
aesthetics <- "alpha"
scale_name <- "alpha_c"
pallette <- rescale_pal(range)
})
#> function (name = waiver(), breaks = waiver(), minor_breaks = waiver(),
#> labels = waiver(), limits = NULL, oob = censor, expand = waiver(),
#> na.value = NA_real_, trans = "identity", guide = "legend",
#> range = c(0.1, 1))
#> {
#> {
#> aesthetics <- "alpha"
#> scale_name <- "alpha_c"
#> pallette <- rescale_pal(range)
#> }
#> continuous_scale(aesthetics = aesthetics, scale_name = scale_name,
#> palette = palette, name = name, breaks = breaks, minor_breaks = minor_breaks,
#> labels = labels, limits = limits, oob = oob, expand = expand,
#> na.value = na.value, trans = trans, guide = guide)
#> }
#> <environment: namespace:ggplot2> |
@thomasp85 I updated the functions a bit, let me know how that looks. |
Except I'm on a phone so I'm reluctant to try atm but I can probably modify your version to give me what I want |
I don't think it matters what is in |
I'll try to break it tomorrow😉 |
Ah, now I see what it's doing. Yeah it should work, at the expense of both defining And I do agree that the resulting function is more comprehensible... @hadley should I proceed down this road? |
Neither of those approaches seem particularly clear to me — I'd like something that does a better job of illuminating the relationship between the two functions. Let me think about it for a bit. |
Wouldn't some detailed documentation and comments help? I think you'll have a hard time finding an approach that both eleminate ellipsis, gracefully handles new arguments to |
It's not the implementation of the |
So it's the nature of the generated function? The last of Jims iterations almost mirrors the current source code except for the fenced block |
Oh you mean the verbosity of the call to |
Yeah, I don't find the call to |
I'll try to ponder on a better syntax |
What about a syntax such as this: forward(
args = list(range = c(0.1, 1)),
code = {
aesthetics <- "alpha"
scale_name <- "alpha_c"
pallette <- rescale_pal(range)
},
to = continuous_scale,
redefine = c("palette", "aesthetics", "scale_name"),
use.defaults = c("super", "position", "rescaler")
) I think you can almost read that aloud and make sense of it...
|
@thomasp85 I like that syntax, I would prefer it be |
I think this is better, but not good enough to be worth finishing off for this release. |
This old issue has been automatically locked. If you believe you have found a related problem, please file a new issue (with reprex) and link to this issue. https://reprex.tidyverse.org/ |
Fixes #1750, #1735, and #1737