do.call specify environment inside function
Asked Answered
M

3

6

I'm using the following construct in a package,

## two functions in the global environment
funa <- function(x) x^2
funb <- function(x) x^3
## called within a function, fine
fun_wrap <- function(){
  lapply(c('funa', 'funb'), do.call, list(x=3))
}

fun_wrap()
[[1]]
[1] 9

[[2]]
[1] 27

but I've just been bitten by the fact that it won't work if the functions are in a different (local) frame,

## same construct, but the functions are local
fun_wrap1 <- function(){
  funa1 <- function(x) x^2
  funb1 <- function(x) x^3
  lapply(c('funa1', 'funb1'), do.call, list(x=3))
}
## now it fails
fun_wrap1()
##Error in FUN(c("funa1", "funb1")[[1L]], ...) : 
##  could not find function "funa1"

I've tried passing envir=parent.frame(2) to do.call() (doesn't work); frankly the help page of ?parent.frame goes way over my head. Any hint for a more robust use of do.call?

Note that the list of functions comes as a character vector, passed from another piece of code; I prefer not to pass the functions directly.

Edit: one more twist... I thought I'd illustrated the right problem with my toy example, but the actual code I'm using is slightly different, in the sense that I'm calling fun_wrap1 within a separate function. The proposed solutions fail in this context.

fun_wrap1 <- function(funs){
  lapply(funs, do.call, args=list(x=3), envir=environment())
}

foo <- function(){
  funa1 <- function(x) x^2
  funb1 <- function(x) x^3
 fun_wrap1(c('funa1', 'funb1'))
}

foo()
##Error in FUN(c("funa1", "funb1")[[1L]], ...) : 
##  could not find function "funa1"

(and the same happens with the match.fun approach)

I can get it to work by passing an optional environment to fun_wrap1,

fun_wrap1 <- function(funs, e=parent.frame()){
  lapply(funs, do.call, args=list(x=3), envir=e)
}

foo <- function(){
  funa1 <- function(x) x^2
  funb1 <- function(x) x^3
  fun_wrap1(c('funa1', 'funb1'))
}

foo()

and that's hopefully it.

Mal answered 25/9, 2014 at 0:12 Comment(5)
sorry about the constant changes, I'd failed to properly identify the target when I wrote the first iteration.Mal
In your revised example that fails, the new function should be changed to: fun_wrap1 <- function(funs, envir = parent.frame()) { lapply(funs, do.call, args=list(x=3), envir=envir) } The foo function can stay as it is.Plinth
Thanks, I've changed it according to your advice.Mal
You don't need environment() in foo.Plinth
even better! Boy, I really need to get my head around those things.Mal
F
5

This seems to work, but i'm not sure if it has other implications I'm not considering:

fun_wrap1 <- function(){
  funa1 <- function(x) x^2
  funb1 <- function(x) x^3
  lapply(c('funa1', 'funb1'), do.call, args=list(x=3), envir=environment())
}

fun_wrap1()
#[[1]]
#[1] 9
#
#[[2]]
#[1] 27

So this is essentially equivalent to having the lapply statement as:

lapply(
       c('funa1', 'funb1'), 
       function(f) do.call(f, args=list(x=3), envir=environment() )
      ) 
Farnsworth answered 25/9, 2014 at 0:43 Comment(1)
@Mal - it should probably be args=list(x=3) to be really explicit.Farnsworth
P
2

Evidently if we evaluate the functions in fun_wrap2 it works. The problem with the approach in the question is that the character strings get converted to functions inside one of the processing functions which changes the lookup path.

fun_wrap2 <- function(){

  funa1 <- function(x) x^2
  funb1 <- function(x) x^3

  nms <- c("funa1", "funb1")
  funs <- lapply(nms, match.fun)
  lapply(funs, do.call, list(x=3))

}

fun_wrap2()
Plinth answered 25/9, 2014 at 0:32 Comment(2)
that makes sense, thanks for the added explanation. I've since realised that my problem is slightly different though, and I can't get this approach to work there (see Edit)Mal
while I really appreciate the help, i've accepted the other answer as it clarified the explicit passing of the right environment, which was key to the problem. match.fun achieved the same here, but perhaps not as explicitly.Mal
S
0

A slightly simpler version of @g-grothendieck's answer. Rather than using the function names, we just put the functions themselves into the list that is fed to lapply.

fun_wrap1 <- function(){
  funa1 <- function(x) x^2
  funb1 <- function(x) x^3
  lapply(list(funa1, funb1), do.call, list(x=3))
}

fun_wrap1()
Seville answered 25/9, 2014 at 0:36 Comment(1)
That was my original answer but he indicated in the comments that the input must be a character vector of names so I changed it.Plinth

© 2022 - 2024 — McMap. All rights reserved.