Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 840
andrewH Avatar asked Oct 05 '18 22:10

andrewH


2 Answers

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.

like image 73
G. Grothendieck Avatar answered Nov 14 '22 07:11

G. Grothendieck


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.

like image 3
coffeinjunky Avatar answered Nov 14 '22 05:11

coffeinjunky