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.