4

The documentation of do.call states:

If quote is FALSE, the default, then the arguments are evaluated (in the calling environment, not in envir).

This sentence would suggest to me that when quote = FALSE, specifying envir makes no difference. However, that's not the case, and in fact I've encountered cases where I need to specify envir to get the function to work.

Simplest reproducible example:

g1 <- function(x) {
  args <- as.list(match.call())
  args[[1]] <- NULL # remove the function call
  do.call(print, args, quote = FALSE) # call print()
}

g2 <- function(x) {
  args <- as.list(match.call())
  args[[1]] <- NULL # remove the function call
  do.call(print, args, quote = FALSE, envir = parent.frame()) # call print(), specifying envir
}

h1 <- function(x, y) {
  g1(x*y)
}

h2 <- function(x, y) {
  g2(x*y)
}

With these functions, h2() behaves as one would think but h1() does not:

h1(2, 3)
#Error in print(x) : object 'y' not found

h2(2, 3)
#[1] 6

y <- 100
h1(2, 3)
#[1] 600 
## Looks like g1() took the value of y from the global environment

h2(2, 3)
#[1] 6

Can anybody explain to me what's going on here?

Note: There's a related post here but by my reading the answers don't specifically state what do.call() does with the envir variable.

Claus Wilke
  • 16,992
  • 7
  • 53
  • 104

1 Answers1

5

?do.call says:

envir
an environment within which to evaluate the call. This will be most useful if what is a character string and the arguments are symbols or quoted expressions.

We can easily illustrate this if the what= argument of do.call is a character string. Then envir= determines where it is looked up.

e <- new.env()
e$f <- function() 2
f <- function() 3
do.call("f", list())
## [1] 3
do.call("f", list(), envir = e)
## [1] 2

The same is true for the arguments as the code in the question shows. Note that the arguments are already quoted since match.call() is being used.

What is happening in the case of h1 and g1 is that this is effectively run within g1

do.call(print, list(call("*", quote(x), quote(y))), quote = FALSE)

Now it finds x in g1 (since g1 has one argument x) but there is no y in g1 so it looks to the parent environment of g1 which is the global environment where it finds y.

In the case of h2 and g2 it runs this in g2:

do.call(print, list(call("*", quote(x), quote(y))), quote = FALSE, envir = parent.frame())

and it finds x and y in h2 which is the parentframe of g2.

Note that the parent environment is not the same as the parent frame:

  • the parent environment is determined by where the function was defined so if the function was defined in the global environment then its parent environment is the global environment.
  • the parent frame is the environment of the caller
G. Grothendieck
  • 254,981
  • 17
  • 203
  • 341
  • Thanks. So I take it then that `quote=FALSE` has no effect on this case and the call in `g2` is the correct way to do it if I want to simulate a regular function call? – Claus Wilke Dec 10 '17 at 00:56