My goal is to take additional arguments passed through the ellipsis ... (see ?dots for more info) and build a new generic function with the parameters already set and pass this to another function.
For example, given the two functions:
foo <- function(v, FUN, ...) {
## code here to build NEWFUN
SomeFun(v, NEWFUN)
}
bar <- function(v, FUN) {
SomeFun(v, FUN)
}
I would like to be able to do this in foo:
bar(x, FUN = \(x) paste(x, collapse = ", "))
By calling foo(x, paste, collapse = ", ").
My Attempt
We start with a simple function takes a base R function (here paste) and applys it to a vector. Note, I'm trying to make this as simple as possible, so I have removed sanity checks. Also, I wrote this to be demonstrated only with the base R function paste.
FunAssign <- function(f, x) f(x)
And here is my naive attempt:
foo <- function(v, FUN, ...) {
FUN <- \(x) FUN(x, ...)
FunAssign(FUN, v)
}
And calling it, we get the error:
foo(letters[1:5], paste, collapse = ", ")
#> Error in FUN(x, ...) : unused argument (collapse = ", ")
As explained above, the desired output when calling foo(letters[1:5], paste, collapse = ", ") can be emulated by calling bar like so:
bar <- function(v, FUN) FunAssign(FUN, v)
bar(letters[1:5], FUN = \(x) paste(x, collapse = ", "))
#> [1] "a, b, c, d, e"
I thought my attempt with foo would work because if we do something like the below it appears that we are on the right track:
baz <- function(FUN, ...) \(x) FUN(x, ...)
baz(paste, collapse = ", ")(letters[1:5])
#> [1] "a, b, c, d, e"
I have found several resources that almost get at what I'm after, but nothing that quite hits the spot.
- R: using a list for ellipsis arguments. This one is good, however
do.callevaluates the expression. - And then there is How to create an R function programmatically?. But again, this looks like it is evaluating the function and thus returning a value.
I think your first idea would work if you didn't overwrite the
FUNvariable infoo(). That is, this works for me:Besides renaming the second
FUNinfoo(), I added theforce(FUN)command. This is not necessary in this example, but generally speaking it's a good idea to make sure arguments that are needed in a created function are evaluated. I think that automatically happens in this code (sinceFunAssignwill use it), but I'm superstitious about things like that.Edited to add: the explanation for this is fairly simple.
FUNin the functionFUN2()is not evaluated untilFUN2()is called.FUN2is called withinFunAssign(FUN2, v), R tries to evaluateFUN(x, ...).FUNis not defined in that function, R looks in the parent environment, and findsFUNin the evaluation frame offoo()as an argument to the function call.FUN, that lookup happened after you had overwritten the argument with a different variable (the function I calledFUN2).FUN(x, ...), in your code theFUNit is working with is the one that was defined right there, and R attempts a recursive call, but gives an error because that function only has one argument namedx.The key thing here is that the body of a function is just an unevaluated expression. It doesn't get evaluated until you call the function, and that's when R tries to find all the objects it uses, including the functions it calls.