Unpacking argument lists for ellipsis in R
Asked Answered
C

3

57

I am confused by the use of the ellipsis (...) in some functions, i.e. how to pass an object containing the arguments as a single argument.

In Python it is called "unpacking argument lists", e.g.

>>> range(3, 6)             # normal call with separate arguments
[3, 4, 5]
>>> args = [3, 6]
>>> range(*args)            # call with arguments unpacked from a list
[3, 4, 5]

In R for instance you have the function file.path(...) that uses an ellipsis. I would like to have this behaviour:

> args <- c('baz', 'foob') 
> file.path('/foo/bar/', args)
[1] 'foo/bar/baz/foob'

Instead, I get

[1] 'foo/bar/baz' 'foo/bar/foob'

where the elements of args are not "unpacked" and evaluated at the same time. Is there a R equivalent to Pythons *arg?

Cerebro answered 5/8, 2010 at 11:9 Comment(0)
L
51

The syntax is not as beautiful, but this does the trick:

do.call(file.path,as.list(c("/foo/bar",args)))

do.call takes two arguments: a function and a list of arguments to call that function with.

Luminesce answered 5/8, 2010 at 11:26 Comment(2)
An extended discussion on this technique: r-bloggers.com/a-new-r-trick-for-me-at-leastFemmine
...and another explanation, also from R Bloggers, which helped me a lot: r-bloggers.com/2016/09/breaking-the-ellipsisTanika
E
24

You can extract information from the ellipsis by calling list(...) inside the function. In this case, the info in the ellipsis is packaged as a list object. For example:

> foo <- function(x,...){
+   print(list(...))
+ }
> foo(1:10,bar = 'bar','foobar')
$bar
[1] "bar"

[[2]]
[1] "foobar"

You can achieve the desired behaviour from vectorised functions like file.path with a call to do.call, which is sometimes simpler to use with the wrapper splat (in the plyr package)

> args <- c('baz', 'foob')
> library(plyr)
> splat(file.path)(c('/foo/bar', args))
[1] "/foo/bar/baz/foob"
Eric answered 5/8, 2010 at 11:19 Comment(0)
T
10

It took me a while to find it, but the purrr package has an equivalent to plyr::splat: it's called lift_dl.

The "dl" in the name stands for "dots to list", as it's part of a series of lift_xy functions that can be used to "lift" the domain of a function from one kind of input to another kind, these "kinds" being lists, vectors and "dots".

Since lift_dl is probably the most useful of those, there is a simple lift alias provided for it.

To reuse the above example:

library(purrr)
args <- c("baz", "foob")
lift(file.path)(c("/foo/bar", args))
# [1] "/foo/bar/baz/foob"

Update

As of purrr 1.0.0, the lift_* family of functions is deprecated.

Moving forward, one should instead use rlang::inject to enable splicing via the !!! operator:

library(rlang)
args <- c("baz", "foob")
inject(file.path("/foo/bar", !!!args))
#> [1] "/foo/bar/baz/foob"
Trilemma answered 8/10, 2018 at 10:0 Comment(1)
This is brilliant!Retention

© 2022 - 2024 — McMap. All rights reserved.