Reputation: 2977
Context: I am trying to use a function (caller) that calls other functions (callees). Each callee function uses a set of arguments that can be different from a function to another.
Problem: it seems like I have to use arguments in the caller function in order to have them passed to the callees functions (see fun_d
in the example below).
Question: how to use ellipsis to avoid using explicit arguments in the caller function?
Using ellipsis in nested functions and Running a function with multiple arguments when an argument is not used seems not to answer this (or I do not understand correctly).
Reprex:
# fun_a and fun_b have the same set of argument but do not do the same things with them
fun_a <- function(x, y, ...){x+y}
fun_b <- function(x, y, ...){x-y}
# I would like to use fun_a and fun_b in another function (fun_c) AND, have the possibility to
# give different values for each argument of fun_a and fun_b (ex: y1 and y2)
# I thought I could use the ellipsis like in fun_c:
fun_c <- function(...){
fa <- fun_a(x = x, y = y1)
fb <- fun_b(x = x, y = y2)
paste(fa, fb)
}
# fun_d works but I you like to understand why func_c does not
fun_d <- function(x, y1, y2, ...){
fa <- fun_a(x = x, y = y1)
fb <- fun_b(x = x, y = y2)
paste(fa, fb)
}
mapply(FUN = fun_c, x = c(1, 2, 3), y1 = c(1, 2, 3), y2 = c(0, 0, 0)) # not working, it says "x" is missing (and I suppose "y" too)
#> Error in fun_a(x = x, y = y1): object 'x' not found
mapply(FUN = fun_d, x = c(1, 2, 3), y1 = c(1, 2, 3), y2 = c(0, 0, 0)) # working
#> [1] "2 1" "4 2" "6 3"
Created on 2021-06-23 by the reprex package (v2.0.0)
Upvotes: 1
Views: 557
Reputation: 545498
...
allows callers of a function to pass arbitrary arguments into the function.
But it does not create corresponding parameter variables inside the function. If you want to use arguments passed via ...
inside a function, you have these choices:
You can pass ...
on as-is. For example:
print_two_vectors = function (x, y, ...) {
print(x, ...)
print(y, ...)
}
This can be used to pass arbitrary arguments on to the regular print
function:
print_two_vectors(pi, exp(1), digits = 2L)
[1] 3.1
[1] 2.7
This should be the most frequent use of ...
. For most other purposes, you should instead accept regular parameters.
You can access them in order via ..1
, ..2
, etc. ...elt(n)
gives you the nth argument. To find out how many arguments are passed, you can use ...length()
:
example = function (...) {
message('Got ', ...length(), ' arguments. The first two are: ', toString(c(..1, ..2)))
message('The last one is: ', toString(...elt(...length())))
}
Here’s how the output would look like:
example(1, 2, 3, 4)
Got 4 arguments. The first two are: 1, 2
The last one is: 4
You can unpack them into a list. Strictly speaking, this is the same as (1), i.e. you’re just passing ...
on to the list
function. This allows you to access the elements by name, since lists can be named:
add_xy = function (...) {
args = list(...)
args$x + args$y
}
add_xy(x = 1, y = 2)
[1] 3
… OK, that example is a bit useless. But you can use the same to solve your issue.
You can access the value of ...
in an unevaluated context. This is an advanced topic. It’s not often useful in “regular” usage of R, but it becomes powerful when using non-standard evaluation for metaprogramming.
add_xy2 = function (...) {
call = match.call()
eval.parent(call$x) + eval.parent(call$y)
}
add_xy2(x = 1, y = 2)
[1] 3
Upvotes: 8
Reputation: 2626
In my experience, this issue can often be solved by pulling the arguments into the namespace like so (untested):
fun_c <- function(...){
with(list(...), {
fa <- fun_a(x = x, y = y1)
fb <- fun_b(x = x, y = y2)
paste(fa, fb)
})
}
Upvotes: 4