How to expand an ellipsis (...) argument without evaluating it in R
The most idiomatic way is:
f <- function(x, y, ...) {
match.call(expand.dots = FALSE)$`...`
}
How to use ellipsis to pass arguments and evaluate in another environment in R
Do you really need to delay evaluation of the parameters? Seems like
getExpression <- function(...){
return(list(...))
}
ex1 <- getExpression(a=1:3,b=pmax(2:4,3:5),c=c("test","test1","test2"))
do.call("SomeClass_1", ex1)
do.call("SomeClass_2", ex1)
Would work better. If you want to expand the parameters for the class call, that call needs needs to be invoked with the do.call
, not just the parameters.
R: How can a function accept variable arguments using ellipsis (...) without copying them in memory?
We can expand ...
arguments using match.call
and then evaluate and store the arguments in an environment
which will not copy the values. Since environment
objects require names for all elements and don't preserve their ordering, we need to store a separate vector of ordered tag names in addition to the (optional) formal argument names. Implemented here using attributes:
argsenv <- function(..., parent=parent.frame()) {
cl <- match.call(expand.dots=TRUE)
e <- new.env(parent=parent)
pf <- parent.frame()
JJ <- seq_len(length(cl) - 1)
tagnames <- sprintf(".v%d", JJ)
for (i in JJ) e[[tagnames[i]]] <- eval(cl[[i+1]], envir=pf)
attr(e, "tagnames") <- tagnames
attr(e, "formalnames") <- names(cl)[-1]
class(e) <- c("environment", "argsenv")
e
}
Now we can use it in our functions instead of list(...)
:
f <- function(...) {
dots <- argsenv(...)
# Let's print them out.
for (i in seq_along(attr(dots, "tagnames"))) {
cat(i, ": name=", attr(dots, "formalnames")[i], "\n", sep="")
print(dots[[attr(dots, "tagnames")[i]]])
}
}
> f(10, a=20)
1: name=
[1] 10
2: name=a
[1] 20
So it works, but does it avoid copying?
g1 <- function(...) {
dots <- list(...)
for (x in dots) .Internal(inspect(x))
}
> z <- 10
> .Internal(inspect(z))
@10d854908 14 REALSXP g0c1 [NAM(2)] (len=1, tl=0) 10
> g1(z)
@10dcdaba8 14 REALSXP g0c1 [NAM(2)] (len=1, tl=0) 10
> g1(z, z)
@10dcbb558 14 REALSXP g0c1 [NAM(2)] (len=1, tl=0) 10
@10dcd53d8 14 REALSXP g0c1 [NAM(2)] (len=1, tl=0) 10
>
g2 <- function(...) {
dots <- argsenv(...);
for (x in attr(dots, "tagnames")) .Internal(inspect(dots[[x]]))
}
> .Internal(inspect(z))
@10d854908 14 REALSXP g0c1 [MARK,NAM(2)] (len=1, tl=0) 10
> g2(z)
@10d854908 14 REALSXP g0c1 [MARK,NAM(2)] (len=1, tl=0) 10
> g2(z, z)
@10d854908 14 REALSXP g0c1 [MARK,NAM(2)] (len=1, tl=0) 10
@10d854908 14 REALSXP g0c1 [MARK,NAM(2)] (len=1, tl=0) 10
You could implement this in S4 with slots instead of attributes, define all sorts of methods (length
, [
, [[
, c
, etc.) for it, and turn it into a full-fledged general-purpose non-copying replacement for list
. But that's another post.
Side note: You can avoid mapply
/Map
by rewriting all such calls as lapply(seq_along(v1) function(i) FUN(v1[[i]], v2[[i]],
...)
, but that's a lot of work and doesn't do your code any favors in elegance and readability. Instead, we can rewrite the mapply
/Map
functions using argsenv
and some expression manipulation to do exactly that inside:
mapply2 <- function(FUN, ..., MoreArgs=NULL, SIMPLIFY=TRUE, USE.NAMES=TRUE) {
FUN <- match.fun(FUN)
args <- argsenv(...)
tags <- attr(args, "tagnames")
iexpr <- quote(.v1[[i]])
iargs <- lapply(tags, function(x) { iexpr[[2]] <- as.name(x); iexpr })
names(iargs) <- attr(args, "formalnames")
iargs <- c(iargs, as.name("..."))
icall <- quote(function(i, ...) FUN())[-4]
icall[[3]] <- as.call(c(quote(FUN), iargs))
ifun <- eval(icall, envir=args)
lens <- sapply(tags, function(x) length(args[[x]]))
maxlen <- if (length(lens) == 0) 0 else max(lens)
if (any(lens != maxlen)) stop("Unequal lengths; recycle not implemented")
answer <- do.call(lapply, c(list(seq_len(maxlen), ifun), MoreArgs))
# The rest is from the original mapply code.
if (USE.NAMES && length(tags)) {
arg1 <- args[[tags[1L]]]
if (is.null(names1 <- names(arg1)) && is.character(arg1)) names(answer) <- arg1
else if (!is.null(names1)) names(answer) <- names1
}
if (!identical(SIMPLIFY, FALSE) && length(answer))
simplify2array(answer, higher = (SIMPLIFY == "array"))
else answer
}
# Original Map code, but calling mapply2 instead.
Map2 <- function (f, ...) {
f <- match.fun(f)
mapply2(FUN=f, ..., SIMPLIFY=FALSE)
}
You could even name them mapply
/Map
in your package/global namespace to shadow the base
versions and not have to modify the rest of your code. The implementation here is only missing the unequal length recycling feature, which you could add if you wanted to.
Unpacking argument lists for ellipsis in R
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.
How to do lazy-evaluation of ... function arguments in R?
1) Use eval(substitute(...))
myfunc <- function(X, ...) {
for (foo in seq_along(X)) {
eval(substitute(cat(..., "\n")))
}
}
myfunc(X = 1:5, foo, 'bar')
giving:
1 bar
2 bar
3 bar
4 bar
5 bar
2) defmacro Another approach is to create a macro using defmacro
in gtools:
library(gtools)
myfunc2 <- defmacro(X, DOTS, expr = {
for (foo in seq_along(X)) {
cat(..., "\n")
}
})
myfunc2(X = 1:5, foo, 'bar')
R: why providing a list to ellipsis (...) when ellipsis is the last argument does not work?
The way arguments work in R is that when you don't name your first argument, R just assumes that goes into the first argument for the function. This is true for the second, third and so forth arguments in the argument list EXCEPT for arguments that come after the ...
- because R doesn't know how many arguments you intend to fit into the ...
, if you want to change the default of whatever comes after it, you have to name it.
So in your case, when you call function f()
, the object list(a=2)
goes into the ...
. But in g()
, that same object goes into a. The only way you can get something into ...
when that is placed at the end of the argument list without including arguements for a and b is to name it something that isn't a or b, e.g. g(c=list(a=1)).
How to check if any arguments were passed via ... (ellipsis) in R? Is missing(...) valid?
Here's an alternative that will throw an error if you try to pass in a non-existent object.
test2 <- function(...) if(length(list(...))) FALSE else TRUE
test2()
#[1] TRUE
test2(something)
#Error in test2(something) : object 'something' not found
test2(1)
#[1] FALSE
Ignore argument missing error in ellipsis function
R is an odd beast because even though the syntax clearly allows trailing commas, it doesn’t just discard/ignore them, as other languages do. Instead, R pretends that a “missing” argument has been passed. As long as nothing touches the missing argument, that’s a-ok. We can even use this situation without dots:
f = function (a, b, c) a + b
Since c
is never read, we don’t need to pass it:
f(1, 2) # works
f(1, 2, ) # works, too
Not very useful, perhaps. But the following also works:
g = function (a, b, c) a + b + if (missing(c)) 0 else c
g(1, 2) # 3
g(1, 2, ) # 3
g(1, 2, 3) # 6
… unfortunately this doesn’t help us directly to capture ...
.
Short of using a read-made solution (e.g. rlang::list2
), the only real way to capture dots (allowing trailing commas) is to work on unevaluated arguments, and to manually evaluate them (we might instead be tempted to try missing(...elt(...length()))
; alas R doesn’t accept that).
There are different ways of getting at the unevaluated dot arguments. The simplest way is to use match.call(expand.dots = FALSE)$...
. Which leaves us with:
new_game = function (...) {
args = match.call(expand.dots = FALSE)$...
nargs = length(args)
if (identical(args[[nargs]], quote(expr = ))) nargs = nargs - 1L
lapply(args[seq_len(nargs)], eval, envir = parent.frame())
}
Evaluate elipsis (dots) multiple times, substitute arguments
Here is my solution, any alternative will be enjoyed.
The things is to substitute the variable by it's value at the moment we call the function.
main_fun_solution <- function(arg_A, arg_B) {
eval(substitute(main_fun(fun_A(arg_A),fun_B(arg_B), times = 3), list("arg_A" = arg_A, "arg_B" = arg_B)))
}
main_fun_solution(1,2)
NB: list("arg_A" = arg_A, "arg_B" = arg_B)` makes my heart bleed (the overall solution actually)
R: How to use list elements like arguments in ellipsis?
This works, but I'm mystified by the design choices in that function:
lst <- list(
a=structure(1:3, class="My_Class"),
b=structure(letters[1:3], class="My_Class")
)
env <- list2env(lst)
call <- as.call(append(list(fun), names(lst)))
eval(call, env)
To trick the function into working, you have to evaluate it in an environment where your list items are objects (this is what list2env
does, creates that environment).
The terrible thing about that function is that it looks up objects in parent.frame
based on their names, assuming that the ...
values are indeed names. I really don't understand why that function doesn't just do something like:
if(!all(vapply(list(...), class, "") == "My_Class")) stop(...)
Related Topics
Extracting Coefficient Variable Names from Glmnet into a Data.Frame
How to Add Another Layer/New Series to a Ggplot
Print String and Variable Contents on the Same Line in R
Plot Random Effects from Lmer (Lme4 Package) Using Qqmath or Dotplot: How to Make It Look Fancy
Change Color of Leaflet Marker
How to Add an Inset (Subplot) to "Topright" of an R Plot
How to Add Boxplots to Scatterplot with Jitter
Reorder Rows Using Custom Order
What Is a Good Way to Read Line-By-Line in R
Can't Change Fonts in Ggplot/Geom_Text
Dplyr - Summary Table for Multiple Variables
Monitoring for Changes in File(S) in Real Time
Dplyr: Put Count Occurrences into New Variable
Create an Expression from a Function for Data.Table to Eval
Change Size of Axes Title and Labels in Ggplot2