How to Expand an Ellipsis (...) Argument Without Evaluating It in R

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



Leave a reply



Submit