8

In Hadley's Advanced R book, there is a piece of code that I cannot understand the output.

f <- function(x) substitute(x)
g <- function(x) deparse(f(x))
g(1:10)
g(x)
g(x + y ^ 2 / z + exp(a * sin(b)))

Why do they all return "x"? Especially when

g <- function(x) deparse(substitute(x))

returns the "1:10", "x", and "x + y ^ 2 / z + exp(a * sin(b))" as expected.

Heisenberg
  • 8,386
  • 12
  • 53
  • 102

2 Answers2

11

First, some background information: A promise is an unevaluated argument. A promises comprises of two parts: 1) the code / expression that gives rise to this delayed computation (this code can be viewed by substitute or pryr::promise_info), and 2) the environment where this code / expression is created and should be evaluated in (this environment can be viewed by pryr::promise_info).

The question is also clearer if you changed the g() function to

g <- function(x) deparse(f(whatever))

you would always get "whatever". This is because when g() calls f(whatever), it passes a promise object to f()--this object has the code whatever and the environment of g()'s execution environment. Then, the substitute within f() looks at this promise object and returns the code / expression of that promise, which is whatever in this case.

The code and the environment of the promise object can be confirmed by running the following code:

library(pryr) # need to install it
f <- function(x) {
  print(promise_info(x))
  substitute(x)
}

g <- function(x) {
  print(environment())
  f(whatever)
}
g(1:10)

The bottom line is you'll get back whatever you pass to f(whatever). That's why it's not a good idea to separate these functions. One work around would be to use

g <- function(...) deparse(f(...))

This way the parameter is passed through to f() and not renamed in g().

On the other hand, g <- function(x) deparse(substitute(x)); g(1:10) produces 1:10 because, in this case, substitute is looking at promise object x (in contrasts to the promise object whatever in the above case). Promise x here has the code 1:10 and the environment R_GlobalEnv. (Again, this can be checked using g <- function(x) { print(promise_info(x) ; deparse(substitute(x)). So substitute(x) returns 1:10 as expected.

Heisenberg
  • 8,386
  • 12
  • 53
  • 102
MrFlick
  • 195,160
  • 17
  • 277
  • 295
  • But that doesn't explain why `deparse(substitute(x))` gives the correct response. In this case, `g()` is calling `substitute(x)`, so when `substitute()` looks for the expression that was passed to it, it always sees `x`. Why is that not true? – Heisenberg Aug 16 '14 at 14:00
  • `deparse(substitute(x))` works because you don't have the second level of redirection. The `x` that's in `f()` and `g()` is different. Really `substitute` is working on the promise object that's passed to the function. Once you call another function, that promise has to be resolved. So if you do it within the same function call, you don't have that problem. – MrFlick Aug 16 '14 at 19:16
  • "Really `substitute` is working on the promise object that's passed to the function." What is the promise object and the function you're referring to? (just clarification) – Heisenberg Aug 17 '14 at 03:13
  • From docs "If it is a promise object, i.e., a formal argument to a function or explicitly created using delayedAssign(), the expression slot of the promise replaces the symbol." Could you clarify what is the expression slot and the symbol in this case? – Heisenberg Aug 17 '14 at 03:19
  • @Heisenberg It has to do with lazy evaluation. Parameters are passed as unevaluated expressions, or more formally, promises. If you never ask for the value, it's never actually passed or created. With `deparse(substitute(x))`, the symbol is `x` and the expression slot contains what you actually passed to the function, eg `1:10`. – MrFlick Aug 17 '14 at 03:23
0

I also struggled with this, here is my take on a clear explanation:

f <- function(x) substitute(x)

g <- function(x) deparse(f(x))

g(5) gives “x”. Why?

First of all the deparse does not make a difference

f <- function(x) substitute(x)

g <- function(x) (f(x))

g(5) delivers x

What happens when we do g(5)?

First of all g is called with argument x = 5. An exec environment is created to execute the function () - or deparse - with as parent the global environment. In this execution environment x = 5. Basically 5 is passed on to the function ()

Then function f, substitute, is called out of the exec environment of g. An exec environment for f is created with as parent the exec of g. An argument is passed , x. Note that this x is just a symbol to indicate that an argument is passed to a function. It is not yet evaluated.

In the exec environment of f, this argument is not evaluated because the only function there is substitute() which by definition does not evaluate. With any normal function, eg sqrt(x), x would need to be evaluated. In the exec environment of f, this x would not be found and R would look one level up to the exec environment of g. There x would be found and the sqrt of x would be taken and returned.

You have to read the chapter “environments” in Hackley’s book in order to understand this.

Eg run following to see what arguments are passed on and what the environments are:

## g <- function(x)(f(whatever))
f <- function(x) {
print("----------")
print("starting f, substitute")
print("promise info:") 
print(promise_info(x))
print("current env:")
print(environment()) 
print("function was called in env:")
print(parent.frame())
print("now do subst") 

substitute(x)
}

g <- function(x) {
print("--------")
print("##g <- function(x)(f(x))")
print("-----")
print("starting g ")
print("promise info:")
## A promises comprises of two parts: 1) the code / expression that gives rise to this delayed computation and
## 2) the environment where this code / expression is created and should be evaluated in.
print(promise_info(x))  
print("current env:")
print(environment())
print("function was called in env:")
print(parent.frame())
print("now do f(x)")
f(x)
}

When g is called, 5 is passed as argument When f is called x is passed as argument (and this x is never evaluated by substitute(), it is called a "promise" and will only be evaluated if and when needed)

Now, consider h <- function(y) deparse(substitute(y))

Run :

h <- function(y) {
print("----------")
print("## h <- function(y) deparse(substitute(y))")
print("-----")
print("starting h")
print("promise info:") 
print(promise_info(y))
print("current env:")
print(environment()) 
print("function was called in env:")
print(parent.frame())
deparse(substitute(y))
}

There is only one stage, an exec environment is created for deparse(substitute()) and 5 is passed on to it as argument.

EddieM
  • 1
  • 2