R: Make named items in ellipsis available in (maybe nested) execution environment
Asked Answered
O

2

6

I would like to be able to pass named objects in the arguments of a function that have been matched to ellipsis (..., AKA dots) to be made generally available in the execution environment of that function, or of functions executed inside that environment wherever defined, as if the arguments had been typed there.

I tried to do this, for a function, a nested function defined outside of that function, and a nested function defined inside of that function, using list2env(), which is supposed to return the elements of its argument list into the parent.frame() environment, which I understand to be the calling environment. Thus:

# Ellipsis in nested functions
inner_f1<- function(x){list2env(list(...)[-1L]); x + b}

outer_f <- function(x, ...){
  list2env(list(...)[-1L])
  in01 <- x + a
  inner_f2 <- function(x){list2env(list(...)[-1L]); print(ls()); x + c}
  in02 <- inner_f2(x)  
  in03 <- inner_f1(x)
  out <- list(in01, in02, in03)
}

outer_f(x=0, a=1, b=2, c=3)

I tried this with and without ...'s in the definitions of the nested functions, but neither works. The desired output would be:

$ in01
[1] 1
$ in02
[1] 2
$ in03
[1] 3

The R help file under "dots" provides no information on passing ... values to interior functions, and the only way it mentions of getting information out of a ... is through the ..(n) method. It refers to "An Introduction to R," but the par example seems to suggest, falsely, that it is sufficient for the interior function to have its own ..., although the par code (not cited there) gets at the contents by doing complicated things to args = list(...), and the R language definition also describes the list(...) method. I have not found the idiom substitute(list(...))[-1], frequently used in the R base packages, officially documented anywhere, but in any case neither this nor the eval(substitute(alist(...))) from from "Advanced R"::Nonstandard Evaluation seem to do what I want.

There are a lot of answers to questions about ...s and nested functions here on stackoverflow, but all of the 15 or so that I read seem more specialized than the generalized method I am seeking.

Obliging answered 5/10, 2018 at 22:31 Comment(0)
A
5

Note that the variables that are available is not identical to the variables listed by ls(). In particular, ls() won't list the variables in the parent environment but the variables in the parent environment are still available as inputs (and also as outputs if you use <<-). We assume you just want the variables available and don't care about ls(). (If you do want to actually inject the variables from the outer function's execution environment into the inner function then pass ... to the inner function and use the same methods as shown here for the outer function.) The following example shows that while b is accessible it is not shown in the output of ls().

f1 <- function() { 
  b <- 1
  f2 <- function() { 
    print(b)  # will print the value of b showing b is accessible
    print(ls()) # no variables will be shown
  }
  f2() 
}
f1()

giving:

[1] 1
character(0)

Now to get back to the question here are some alternatives:

1) with Try with:

inner_fun0 <- function() in1

outer_fun <- function(...) with(list(...), {
  inner_fun <- function() in1
  environment(inner_fun0) <- environment()
  list(in1, inner_fun(), inner_fun0())
})
outer_fun(in1 = 7)

giving:

[[1]]
[1] 7

[[2]]
[1] 7

[[3]]
[1] 7

2) list2env An alternative is to use list2env like this:

outer_fun2 <- function(...) {
  list2env(list(...), environment())
  inner_fun <- function() in1
  environment(inner_fun0) <- environment()
  list(in1, inner_fun(), inner_fun0())
}
outer_fun2(in1 = 7)

giving:

[[1]]
[1] 7

[[2]]
[1] 7

[[3]]
[1] 7

You can construct additional variations using the ideas in overwrite variable in parent function from inner function without making variable outside parent function

Also the proto package could be used to recast all this into an object oriented framework.

Aeolotropic answered 5/10, 2018 at 22:54 Comment(4)
These are really elegant. But I am still puzzled why running list2env in the execution environment does not inject the list elements into that environment without more, since (unless i am misreading the documentation) the parent environment is the default location of the output.Obliging
Does it perhaps create a new environment inside of the parent environment? If so, I wish we had a env2env function to dump the contents without encapsulation.Obliging
@andrewH, If the envir argument of list2env is not specified then a new environment is created on the fly and the components of the list specified in the first argument are injected into that, not into the current environment and not into the parent frame.Aeolotropic
I see. So parent = parent.frame() provides the default enclosure of the new environment, rather than the default environment for the unlisted list elements.Obliging
B
3

An alternative:

# note how I add an argument to specify the exact environment:
inner_f1<- function(x, env, ...){
  with(env, {list2env(list(...)); x + b})
 }

outer_f <- function(x, ...){

  # get the "address", i.e. the environment:
  here <- environment()

  # specify the environment in list2env:
  list2env(list(...), envir = here)
  in01 <- x + a

  inner_f2 <- function(x){list2env(list(...), envir = here); print(ls()); x + c}
  # alternatively, use: list2env(list(...), envir = parent.frame(n=2))

  in02 <- inner_f2(x)  

  in03 <- inner_f1(x, here)

  out <- list(in01, in02, in03)
  return(out)
}

outer_f(x=0, a=1, b=2, c=3)

[1] "x"
[[1]]
[1] 1

[[2]]
[1] 3

[[3]]
[1] 2

In essence, you need to make sure the correct "address" is available for all functions.

Brackett answered 5/10, 2018 at 23:5 Comment(1)
Thanks, @coffeejunky! nice alternative.Obliging

© 2022 - 2024 — McMap. All rights reserved.