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 = ", ")
.
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.
do.call
evaluates the expression.I think your first idea would work if you didn't overwrite the FUN
variable in foo()
. That is, this works for me:
FunAssign <- function(f, x) f(x)
foo <- function(v, FUN, ...) {
force(FUN)
FUN2 <- \(x) FUN(x, ...)
FunAssign(FUN2, v)
}
foo(letters[1:5], paste, collapse = ", ")
Besides renaming the second FUN
in foo()
, I added the force(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 (since FunAssign
will use it), but I'm superstitious about things like that.
Edited to add: the explanation for this is fairly simple.
FUN
in the function FUN2()
is not evaluated until FUN2()
is called.FUN2
is called within FunAssign(FUN2, v)
, R tries to evaluate FUN(x, ...)
.FUN
is not defined in that function, R looks in the parent environment, and finds FUN
in the evaluation frame of foo()
as an argument to the function call.FUN
, that lookup happened after you had overwritten the argument with a different variable (the function I called FUN2
).FUN(x, ...)
, in your code the FUN
it 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 named x
.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.