Substitute

Understanding function substitute()

Andrea Spano www.quantide.com (Quantide)www.quantide.com
2021-12-29

Introduction

The scope of this article is to clarify, at least for me, the behaviour of function substitute().

As a confusing example consider:

x <- 0
substitute( x  )
x

and

f <- function() {
  x <- 0
  substitute(x)
}  
f()
[1] 0

As you can see function substitute() returns different result when called either from within the global environment or within a function environment

Basic Substitute

Assuming some prior knowledge of environment and promise objects you can analyse the documentation for substitute().

Documentation says:

Substitution takes place by examining each component of the parse tree as follows:

In practice, function substitutes() takes to arguments:

The return value of the function depends on the interaction between the two arguments.

Test

We will take into account five cases corresponding to four types of objects to pass to argument expr

  1. Non existing object
  2. Constant
  3. variable
  4. promise
  5. expression

For each object we will examine the interaction with the env argument

Substitute non existing object

When symbol x does not exist within env Symbol x is returned

substitute(expr = x , env = list())
x

Substitute constant

When x is a constant either in env or globalenv the constant is returned.

substitute(0, env = list())
[1] 0
substitute(0, env = globalenv())
[1] 0

Substitute variable

When x is a variable in env and env is not the global environment, the value of the variable is returned.

substitute(x, env = list ( x = 0 ))
[1] 0

When x is a variable globalenv, the symbol x is returned

x <- 0 
substitute(x, env = globalenv())
x

Substitute promise

When x is a promise in env, the the expression slot of the promise is returned.

env <- new.env()
delayedAssign('x', 0, assign.env = env)
substitute(x, env = env)
[1] 0

Note that argument assign.env of function delayedAssign() strictly require an environment.

When the expression is a function formal, therefore a promise, the same rule applies:

f <- function(x) {
  substitute(x)
}
f(x = 0)
[1] 0

When x is a promise in globalenv, the symbol slot of the promise is returned. Basically, as soon as the promise object is accessed the variable is evaluated and then passed to substitute

delayedAssign('x', 0, assign.env = globalenv())
substitute(x, env = globalenv())
x

Substitute expression

When exp is an R expression within env substitute returns the expression

substitute(e, env = list( e = quote(x+1)))
x + 1

When exp is an R expression within globalenv substitute returns the symbol associated to the expression

e <- quote(x+1)
substitute(expr = e, env = globalenv())
e

Recap

When function substitute() is used outside globalenv, it really substitutes

f <- function(){
  a <- 1
  b <- 2
  substitute(a+b+c)
}
f()
1 + 2 + c

When function substitute() is used inside globalenv, no substitution occurs and the names associated to either the variable, the promise or the expression is returned unchanged. In practice, function substitute() within globalenv() works like function quote()

a <- 1
b <- 2
substitute(a+b+c)
a + b + c

Working with substitute

Because of its behaviour, developing and experimenting with function substiute() may be difficult.

In order to overcome this problem, Hadley in adv-r proposes function subs() in package pryr:

pryr::subs
function (x, env = parent.frame()) 
{
    if (identical(env, globalenv())) {
        env <- as.list(env)
    }
    substitute_q(substitute(x), env)
}
<bytecode: 0x562053d5dd10>
<environment: namespace:pryr>

Function subs() has the same behavior either inside or outside globalenv:

Within globalenv:

x <- 1
pryr::subs(x+1)
1 + 1

Outside globalenv:

f <- function(x) {
  pryr::subs(x+1)
}
f(1)
1 + 1

The inner part of function subs() relies on function substitute_q.

A less formal, but more esplicative implementation of substitute_q() is

substitute_q <- function (x, env) {
    call <- substitute(substitute(y, env), list(y = x))
    eval(call)
}

When function substitute_q() is called fron globalenv, it returns the evaluated expression

substitute_q  (quote(a + 1), env = list ( a = 2)) 
2 + 1

as it does when called from any other environment

f <- function(x)  {
  a <- 2
  substitute_q(x, env = environment())
}

f(quote(a+1))
2 + 1

Corrections

If you see mistakes or want to suggest changes, please create an issue on the source repository.

Citation

For attribution, please cite this work as

Spano (2021, Dec. 29). andreaspano blog: Substitute. Retrieved from https://andreaspano.github.io/posts/2021-12-29-substitute/

BibTeX citation

@misc{spano2021substitute,
  author = {Spano, Andrea},
  title = {andreaspano blog: Substitute},
  url = {https://andreaspano.github.io/posts/2021-12-29-substitute/},
  year = {2021}
}