© Thomas Mailund 2017

Thomas Mailund, Metaprogramming in R, 10.1007/978-1-4842-2881-4_5

5. Working with Substitutions

Thomas Mailund

(1)Aarhus N, Denmark

You can take expressions and manipulate them by treating them as strings, but you can also modify them by substituting variables for expressions. In addition, you can build expressions by using more advanced quoting, and you can move back and forth between strings and expressions.

A Little More on Quotes

This chapter starts with quickly revisiting the quote mechanism. You have already seen how to quote expressions , but you can do more than just create verbatim expressions . I have discussed the quote function in some detail, but I showed the bquote function only in passing. The bquote function allows you create expressions where you only partially quote—you can evaluate some values when you create the expression and leave other parts for later. While quote wraps an entire expression in quotes, the bquote function does partial substitution in an expression. You call it with an expression, as you would call quote, but any subexpression you wrap in a call to “dot” (that is, you write .(...)) will not be quoted but will instead be evaluated, and the value will be put into the expression you are constructing. If you use bquote without using .(...), it works just like quote.

quote(x + y)
## x + y
bquote(x + y)
## x + y
bquote(.(2+2) + y)
## 4 + y

You used bquote when you constructed terms for the chain rule in your differentiation program. Here you used bquote(.(dfdz) * .(dzdx)) to construct the product of the differentiated function and the differentiated argument; you constructed dfdz and dzdx by constructing a differentiated function, using bquote again, for dfdz (as in bquote(d(.(function_name), .(var))))) and by calling the diff_expr function for dzdx.

In many situations where you need to create a call object, using bquote can be much simpler than constructing the call object explicitly. For the simple example shown previously, there is not a huge difference.

bquote(.(2+2) + y)
## 4 + y
call("+", 2+2, quote(y))
## 4 + y

However, once you start creating expressions with many nested calls (and you don’t need particularly complex expressions to need multiple nested calls), then explicitly creating calls becomes much more cumbersome than using bquote.

bquote((.(2+2) + x) * y)
## (4 + x) * y
call("*", call("(", call("+", 2+2, quote(x))), quote(y))
## (4 + x) * y

Parsing and Deparsing

When you use quoting to construct expressions, you get objects you can manipulate via recursive functions , but you can also work with expressions as strings and translate between strings and expressions using the functions parse and deparse.

The deparse function translates an expression into the R source code that would be used to create the expression (represented as a string), and the parse function parses a string or a file and returns the expressions in it.

deparse(quote(x + y))
## [1] "x + y"
parse(text = "x + y")
## expression(x + y)

For the call to parse here, you need to specify that you are parsing a text string. Otherwise, parse will assume you are giving it a file name and try to parse the content of that file. The result of the call to parse is not, strictly speaking, an expression. However, the type it writes is expression. That is an unfortunate choice for this type because expression objects are actually lists of expressions. The parse function can parse more than one expression, and sequences of expressions are represented in the expression type. You can get the actual expressions by indexing into this object.

(expr <- parse(text = "x + y; z * x"))
## expression(x + y, z * x)
expr[[1]]
## x + y
expr[[2]]
## z * x

The deparse function is often used when you need a string that represents an R object, for example, for a label in a plot. Many functions extract expressions given as arguments and use those as default labels. There, you cannot just use deparse. That would evaluate the expression you are trying to deparse before you turn it into a string. You need to get the actual argument, and for that, you need the substitute function.

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


x <- 1:4; y <- x**2
f(x + y)
## [1] "c(2, 6, 12, 20)"
g(x + y)
## [1] "x + y"

Substitution

The substitute function replaces variables for values in an expression. The deparse(substitute(x)) construction you just saw exploits this by using substitute to get the expression that the function parameter x refers to before translating it into a string. If you just refer to x, you will force an evaluation of the argument and get the value it evaluates to; instead, because you use substitute, you get the expression that x refers to.

Getting the expression used as a function argument, rather than the value of the expression, is a common use of substitute. Together with deparse, it is used to create labels for plots. It is also used for so-called nonstandard evaluation—functions that do not evaluate their arguments following the default rules for environments. Nonstandard evaluation, which you will return to in the next section, obtains the expressions in arguments using substitute and then evaluates them using eval in environments different from the function’s evaluation environment.

Before you consider evaluating expressions, however, you should get a handle on how substitute works. This depends a little bit on where it is called. In the global environment, substitute doesn’t do anything, at least not unless you give it more arguments than the expression (I get to that shortly). In all other environments, if you just call substitute with an expression, the function will search through the expression and find variables. If it finds a variable that has a value in the current environment—whether it is a promise for a function call or a variable you have assigned values to—it will substitute the variable with the value. If the variable does not have a value in the environment, it is left alone. In the global environment, it leaves all variables alone.

In the following example, you see that substitute(x + y) doesn’t get modified in the global environment, even though the variables x and y are defined. Inside the function environment for f, however, you substitute the two variables with their values.

x <- 2; y <- 3
substitute(x + y)
## x + y
f <- function(x, y) substitute(x + y)
f(2, 3)
## 2 + 3

With substitute, variables are not found the same way as they are in eval. When substitute looks in an environment, it does not follow the parent pointer. If it doesn’t find the variable to substitute in the exact environment in which it is called, it will not look further. So, if you write functions like these:

y - 3
## [1] 0
f <- function(x) substitute(x + y)
f(2)
## 2 + y
g <- function(x) function(y) substitute(x + y)
h <- g(2)
h(3)
## x + 3

the function f, when called, will have x in its evaluation environment and y in the parent environment (which is the global environment), but substitute will substitute only the local variable, x. For h, it will know y as a local variable and x from its closure; however, only y, the local variable, will be substituted.

The actual environment that substitute uses to find variables is given as its second argument. The default is just the current evaluating environment. You can change that by providing either an environment or a list with a variable to value mapping.

e <- new.env(parent = emptyenv())
e$x <- 2
e$y <- 3
substitute(x + y, e)
## 2 + 3
substitute(x + y, list(x = 2, y = 3))
## 2 + 3

Again, substitute will not follow parent pointers. Whether these are set implicitly or explicitly in the environment, you pass on to the function.

x <- 2 ; y <- 3
e <- new.env(parent = globalenv())
substitute(x + y, e)
## x + y
e <- new.env(parent = globalenv())
e$x <- 2
e$y <- 3
e2 <- new.env(parent = e)
substitute(x + y, e2)
## x + y

If you want a variable substituted, you need to make sure it is in the exact environment you provide to substitute.

Substituting Expressions Held in Variables

A common case when you manipulate expressions is that you have a reference to an expression—for example, from a function argument—and you want to modify it. In the global environment, you cannot do this directly with substitute. If you give substitute a variable, it will just return that variable.

expr <- quote(x + y)
substitute(expr)
## expr

This is because substitute doesn’t replace the variable in the global environment. You can get the expression substituted by explicitly giving substitute the expression in an environment or a list.

substitute(expr, list(expr = expr))
## x + y

Usually, though, you don’t manipulate expressions in the global environment, and inside a function you can substitute an expression.

f <- function() {
  expr <- quote(x + y)
  substitute(expr)
}
f()
## x + y

But what if you want to replace, say, y with 2 in the expression here? The substitution, both in the global environment with an explicit list or inside a function, will replace expr with quote(x + y), but you want to take that then and replace y with 2. You cannot just get y from the local environment and give it to substitute explicitly; that won’t work either.

f <- function() {
  expr <- quote(x + y)
  y <- 2
  substitute(expr)
}
f()
## x + y
f <- function() {
  expr <- quote(x + y)
  substitute(expr, list(y = 2))
}
f()
## expr

What you want to do is first replace expr with the expression quote(x + y) and then replace y with 2. So, the natural approach is to write the following code, which will not work:

substitute(substitute(expr, list(expr = expr)), list(y = 2))
## substitute(expr, list(expr = expr))

The problem here is that y doesn’t appear anywhere in the expression given to the outermost substitute, so it won’t be substituted in anywhere. What you get is just the following expression:

substitute(expr, list(expr = expr))

You can evaluate this to get x + y.

eval(substitute(substitute(expr, list(expr = expr)), list(y = 2)))

However, the evaluation first substitutes y into the innermost substitute expression—where there is no y variable—and then substitutes expr into the expression expr. The order is wrong.

To substitute variables in an expression that you hold in another variable, you have to write the expression in the opposite order of what comes naturally. You don’t want to substitute expr at the innermost level and then y at the outermost level; you want to first substitute expr into a substitute expression that takes care of substituting y. The outermost level substitutes expr into substitute(expr, list(y = 2)), which you can evaluate to get y substituted into the expression.

So, you create the expression you need to evaluate like this:

substitute(substitute(expr, list(y = 2)), list(expr = expr))
## substitute(x + y, list(y = 2))

You complete the substitute like this:

eval(substitute(substitute(expr, list(y = 2)), list(expr = expr)))
## x + 2

It might take a little getting used to, but you just have to remember that you need to do the substitutions in this order.

Substituting Function Arguments

Function arguments are passed as unevaluated promises, but the second you access them, they get evaluated. If you want to get hold of the promises without evaluating them, you can use the substitute function. This gives you the argument as an unevaluated—or quoted—expression.

This can be useful if you want to manipulate expressions or evaluate them in ways different from the norm (as you will explore in the next section), but you do throw away information about which context the expression was supposed to be evaluated in. Consider the following example:

f <- function(x) function(y = x) substitute(y)
g <- f(2)
g()
## x
x <- 4
g(x)
## x

In the first call to g, y has its default parameter, which is the one it gets from its closure, so it substitutes to the x that has the value 2. In the second call, however, you have the expression x from the global environment where x is 4. In both cases, you just have the expression quote(x). From inside R, there is no mechanism for getting the environment out of a promise, so you cannot write code that modifies input expressions and then evaluates them in the enclosing scope for default parameters and the calling scope for function arguments.1

You also have to be a little careful when you use substitute in functions that are called with other functions. The expression you get when you substitute is the exact expression a function gets called with. This expression doesn’t propagate through other functions. In the following example, you call the function g with the expression x + y, but since g calls f with expr, that is what you get in the substitution.

f <- function(expr) substitute(expr)
f(x + y)
## x + y
g <- function(expr) f(expr)
g(x + y)
## expr
x <- 2; y <- 3
eval(f(x + y))
## [1] 5
eval(g(x + y))
## x + y

The substitute function is harder to use safely and correctly than using bquote and explicitly modifying call objects, but it is the function you need to use to implement nonstandard evaluation.

Nonstandard Evaluation

Nonstandard evaluation refers to any evaluation that doesn’t follow the rules for how you evaluate expressions in the local evaluation environment. When you use eval to evaluate a function argument in an environment other than the default (which is what you get from a call to environment()), you are evaluating an expression in a nonstandard way.

Typical uses of nonstandard evaluation (NSE) are evaluating expressions in the calling scope, which you have already seen examples of, and evaluating expressions in data frames. You have already seen that you can use a list to provide a variable to value mapping when using substitute, but you can also do the same when using eval.

eval(quote(x + y), list(x = 2, y = 3))
## [1] 5

Since a data frame is just a list of vector elements of the same length, you can also evaluate expressions in the context of data frames.

d <- data.frame(x = 1:2, y = 3:3)
eval(quote(x + y), d)
## [1] 4 5

When you use eval this way, where you explicitly quote the expression, you are not really doing NSE. The quoted expression would not be evaluated in any other, standard way. After all, you explicitly quote it, and if you didn’t quote it here, x+y would be evaluated in the calling scope, not inside the data frame.

x <- 2; y <-  3
eval(x + y, d)
## [1] 5

To do NSE, you have to explicitly substitute an argument, so you do not evaluate the argument-promise in the calling scope and then evaluate it in an alternative scope. For example, you can implement your own version of the with function like this:

my_with <- function(df, expr) {
  eval(substitute(expr), df)
}
d <- data.frame(x = rnorm(5), y = rnorm(5))
my_with(d, x + y)
## [1] -1.4437635  0.9121930 -1.8908632 -0.2156309
## [5]  1.8495491

Here, the expression x + y is not quoted in the function call, so normally you would expect x + y to be evaluated in the calling scope. Because you explicitly use substitute to swap in the argument in my_with, this does not happen. Instead, you evaluate the expression in the context of the data frame. This is nonstandard evaluation.

The real with function works a little better than your version. If the expression you evaluate contains variables that are not found in the data frame, then it takes these variables from the calling scope. Your version can also handle variables that do not appear in the data frame, but it works slightly differently.

If you use the two functions in the global scope, you don’t see a difference.

z <- 1
with(d, x + y + z)
## [1] -0.4437635  1.9121930 -0.8908632  0.7843691
## [5]  2.8495491
my_with(d, x + y + z)
## [1] -0.4437635  1.9121930 -0.8908632  0.7843691
## [5]  2.8495491

However, if you use them inside functions, you do.

f <- function(z) with(d, x + y + z)
f(2)
## [1] 0.5562365 2.9121930 0.1091368 1.7843691
## [5] 3.8495491
g <- function(z) my_with(d, x + y + z)
g(2)
## [1] -0.4437635  1.9121930 -0.8908632  0.7843691
## [5]  2.8495491

What is going on here?

Well, eval takes a third argument that gives the enclosing scope for the evaluation. In my_with you haven’t provided this, so you use the default value, which is the enclosing scope where you call eval, which is the evaluating environment of my_with. You haven’t defined z in this environment, but the enclosing scope includes the global environment where you have defined z.When you evaluate the expression in my_with, you find z in the global environment. In contrast, when you use with, the enclosing environment is the calling environment.

You can change my_with to have the same behavior thusly:

my_with <- function(df, expr) {
  eval(substitute(expr), df, parent.frame())
}


f <- function(z) with(d, x + y + z)
f(2)
## [1] 0.5562365 2.9121930 0.1091368 1.7843691
## [5] 3.8495491
g <- function(z) my_with(d, x + y + z)
g(2)
## [1] 0.5562365 2.9121930 0.1091368 1.7843691
## [5] 3.8495491

Now, you have both typical uses of NSE: evaluating in a data frame and evaluating in the calling scope.

Nonstandard Evaluation from Inside Functions

Nonstandard evaluation is hard to get right once you start using it from inside other functions. It is a convenient approach to simplify the syntax for many operations when you work with R interactively or when you write analysis pipelines in the global scope, but because substitutions tend to work verbatim on the function arguments that you give functions, once arguments get passed from one function to another, NSE gets tricky.

Consider the following example:

x <- 2; y <- 3
f <- function(d, expr) my_with(d, expr)
f(d, x + y)
## [1] 5
g <- function(d, expr) my_with(d, substitute(expr))
g(d, x + y)
## expr

Here, you make two attempts at using my_with from inside a function, and neither works as intended. In f, the expr gets evaluated in the global scope. When you use the variable inside the function, the promise gets evaluated before it is passed along to my_with. In g, you do substitute, but it is substitute(expr) that my_with sees—remember, it does not see the expression as a promise but substitutes it to get an expression—so you don’t actually get the argument substituted. The NSE in my_with prevents this.

If you want functions that do NSE, you really should write functions that work with expressions and do “normal” evaluation on those instead. You can make a version of my_with that expects the expression to be already quoted, which you can use in other functions, and then define my_with to do the NSE like this:

my_with_q <- function(df, expr) {
  eval(expr, df, parent.frame())
}
my_with <- function(df, expr) my_with_q(d, substitute(expr))


g <- function(d, expr) my_with_q(d, substitute(expr))
g(d, x + y)
## [1] -1.4437635  0.9121930 -1.8908632 -0.2156309
## [5]  1.8495491
my_with(d, x + y)
## [1] -1.4437635  0.9121930 -1.8908632 -0.2156309
## [5]  1.8495491

Writing Macros with NSE

Since NSE allows you to evaluate expressions in the calling scope, you can use it to write macros, that is, functions that work as shortcuts for statements where they are called. These differ from functions, which cannot generally modify data outside of their own scope. However, I do not recommend using macros unless you have very good reasons to—the immutability of data is an important feature of R, and violating it with macros goes against this. However, because it is a good example of what you can do with NSE, I will include the example here.

Consider the following code. This example is a modified implementation of the macro code presented in Programmer’s Niche: Macros in R ( https://www.r-project.org/doc/Rnews/Rnews_2001-3.pdf ) by Thomas Lumley (R Journal, 2001). I have simplified the macro-making function a bit; you can see the full version in the original article online.

make_param_names <- function(params) {
  param_names <- names(params)
  if (is.null(param_names))
    param_names <- rep("", length(params))
  for (i in seq_along(param_names)) {
    if (param_names[i] == "") {
      param_names[i] <- paste(params[[i]])
    }
  }
  param_names
}


make_macro <- function(..., body) {
  params <- eval(substitute(alist(...)))
  body <- substitute(body)


  # Construct macro
  f <- eval(substitute(
    function() eval(substitute(body), parent.frame())
  ))


  # Set macro arguments
  param_names <- make_param_names(params)
  names(params) <- param_names
  params <- as.list(params)
  formals(f) <- params


  f
}

The first function simply extracts the names from a list of function parameters and makes sure that all parameters have a name. This is necessary later when you construct the formals list of a function. In a formals list, all parameters must have a name, and you construct these names with this function. The second function is the interesting one. Here you construct a macro by constructing a function whose body will be evaluated in its calling scope. You first get hold of the parameters and body the macro should have. Then you get the parameters into a list by substituting ... in and then evaluating the alist(...) expression . Without the substitution you would get the argument list that just contains ..., but with the substitution you get the arguments that are passed to the make_macro function, except for the named argument body that you just translate into the expression passed to the make_macro with another substitute call.

The function you construct is where the magic happens. You create the expression

substitute(function() eval(substitute(body), parent.frame()))

where body will be replaced with the expression that you pass to the macro. When you evaluate this, you get the function

function() eval(substitute(<body>), parent.frame())

where <body> is not the symbol body but the body expression. This is a function that doesn’t take any arguments (yet) but will substitute variables in <body> that are known in its calling scope—the parent.frame()—before evaluating the resulting expression.

You can see this in action with this small example:

(m <- make_macro(body = x + y))
## function ()
## eval(substitute(x + y), parent.frame())
## <environment: 0x7f8586d49178>

When you call m, it will evaluate x + y in its calling scope, so you can set the variables x and y in the global scope and evaluate it thusly:

x <- 2; y <- 4
m()
## [1] 6

The last part of the make_macro code sets the formal arguments of the macro. It simply takes the parameters you have specified, makes sure they all have a name, and then turns them into a list and sets formals(f). After that, make_macro returns the constructed function.

You can use this function to create a macro that replaces specific values in a column in a data frame with NA like this:

set_NA_val <- make_macro(df, var, na_val,
                         body = df$var[df$var == na_val] <- NA)

The macro you construct takes three parameters: the data frame, df; the variable (column) in the data frame, var; and the value that corresponds to NA, na_val. Its body then is the following expression with df, var, and na_val replaced with the arguments passed to the macro:

df$var[df$var == na_val] <- NA

You can use it like this:

(d <- data.frame(x = c(1,-9,3,4), y = c(1,2,-9,-9)))
##    x  y
## 1  1  1
## 2 -9  2
## 3  3 -9
## 4  4 -9
set_NA_val(d, x, -9); d
##    x  y
## 1  1  1
## 2 NA  2
## 3  3 -9
## 4  4 -9
set_NA_val(d, y, -9); d
##    x  y
## 1  1  1
## 2 NA  2
## 3  3 NA
## 4  4 NA

Here, you see that the constructed set_NA_val macro modifies a data frame in the calling scope. It saves some boilerplate code from being written, but at the cost of keeping parameter values immutable. The more traditional function solution where you return updated values is probably much more readable to most R programmers.

set_NA_val_fun <- function(df, var, na_val) {
  df[df[,var] == na_val, var] <- NA
  df
}
(d <- data.frame(x = c(1,-9,3,4), y = c(1,2,-9,-9)))
##    x  y
## 1  1  1
## 2 -9  2
## 3  3 -9
## 4  4 -9
(d <- set_NA_val_fun(d, "x", -9))
##    x  y
## 1  1  1
## 2 NA  2
## 3  3 -9
## 4  4 -9
(d <- set_NA_val_fun(d, "y", -9))
##    x  y
## 1  1  1
## 2 NA  2
## 3  3 NA
## 4  4 NA

Alternatively, here’s the magrittr pipeline version:

library(magrittr)
d <- data.frame(x = c(1,-9,3,4), y = c(1,2,-9,-9))
d %<>% set_NA_val_fun("x", -9) %>% set_NA_val_fun("y", -9)
d
##    x  y
## 1  1  1
## 2 NA  2
## 3  3 NA
## 4  4 NA

Modifying Environments in Evaluations

The reason you can modify variables in macros is that you can modify environments. Actual values are immutable, but when you modify the data frame in the previous example, you are replacing the reference in the environment to the modified data. If other variables refer to the same data frame, they will still be referring to the original version. Even with macros, you cannot actually modify data, but you can modify environments, and because you can evaluate expressions in environments different from the current evaluating environment, you can make it appear as if you are modifying data in the calling environment.

You can see this by examining the evaluation environment explicitly when you evaluate an assignment. If you explicitly make an environment and evaluate an assignment in it, you see that it gets modified.

e <- list2env(list(x = 2, y = 3))
eval(quote(z <- x + y), e)
as.list(e)
## $z
## [1] 5
##
## $y
## [1] 3
##
## $x
## [1] 2

This environment has the global environment as its parent. Don’t try this with the empty environment as its parent. If you do, it won’t know the <- function. What you see here is that you modify e by setting the variable z.

You can also evaluate expressions in lists, so you can attempt the same here:

l <- list(x = 2, y = 3)
eval(quote(z <- x + y), l)
l
## $x
## [1] 2
##
## $y
## [1] 3

Here you see that the list is not modified. It is only environments you can modify in lists, and while you can evaluate an assignment in a list using eval, you cannot actually modify the list.

Accessing Promises Using the pryr Package

As I mentioned, there is no mechanism in pure R to get access to the internals of promises, so if you use substitute to translate a function argument into its corresponding expression, then you lose information about which environment the expression should be evaluated in. You can, however, use the pryr package to examine promises. This package has the function promise_info that tells you both what the expression is and the environment it belongs to.

Consider this:

library(pryr)
f <- function(x, y) function(z = x + y) promise_info(z)
g <- f(2, 3)
g()
## $code
## x + y
##
## $env
## <environment: 0x7f8586232b78>
##
## $evaled
## [1] FALSE
##
## $value
## NULL
x <- 4; y <- 5
g(x + y)
## $code
## x + y
##
## $env
## <environment: R_GlobalEnv>
##
## $evaled
## [1] FALSE
##
## $value
## NULL

For a promise you get the expression it corresponds to in the code field, the environment it belongs to in the env field, and whether it has been evaluated yet in the evaled field. If it has been evaluated, the corresponding value is in the value field. In the two different calls to g, you see that the code field is the same but the environment is different. In the first call, where you use the default values for parameter z, the environment is the f closure, and in the second, where you call g with an expression from the global environment, the promise environment is also the global environment.

You can see the difference between when a promise has been evaluated and before it is evaluated in the following example:

g <- function(x) {
  cat("=== Before evaluation ===== ")
  print(promise_info(x))
  force(x)
  cat("=== After evaluation ====== ")
  promise_info(x)
}
g(x + y)
## === Before evaluation =====
## $code
## x + y
##
## $env
## <environment: R_GlobalEnv>
##
## $evaled
## [1] FALSE
##
## $value
## NULL
##
## === After evaluation ======
## $code
## x + y
##
## $env
## NULL
##
## $evaled
## [1] TRUE
##
## $value
## [1] 9

The code doesn’t change when you evaluate a promise, but the environment is removed. You do not need to hold a reference to an environment you no longer need, and if you are the only one holding on to this environment, you can free it for garbage collection by no longer holding on to it when you don’t need it any longer. The result of evaluating the promise is put in value, and evaled is set to TRUE.

You can use the promise information to modify an environment and still evaluate it in the right scope. You just need to get hold of the code, which you can get either by the expression

eval(substitute(
  substitute(code, list(y = quote(2 * y))),
  list(code = pi$code)))

or the expression

eval(substitute(
  substitute(expr, list(y = quote(2 * y)))))

where expr is the parameter that holds the promise, and pi is the result of calling promise_info(expr). Neither is particularly pretty, and you have to remember to construct the expressions inside out, but that is the way you can get an expression substituted in for a variable that holds it and then modify it. Of the two, the traditional approach—the second of the two—is probably the simplest.

In both cases, you are creating the expression

substitute(<expr>, list(y = quote(2 * y)))

where <expr> refers to the expression in the promise, and you then evaluate this expression to substitute y for quote(2 * y).

Evaluating the expression once you have modified it is almost trivial in comparison. You can just use eval(expr, pi$env).

f <- function(x, y) function(expr = x + y) {
  pi <- promise_info(expr)
  expr <- eval(substitute(
    substitute(expr, list(y = quote(2 * y)))))
  value <- eval(expr, pi$env)
  list(expr = expr, value = value)
}
g <- f(2, 2)
g()
## $expr
## x + 2 * y
##
## $value
## [1] 6
x <- y <- 4
g(x + y)
## $expr
## x + 2 * y
##
## $value
## [1] 12
z <- 4
g(z)
## $expr
## z
##
## $value
## [1] 4

In the substitution, when you create expr, it is important that you replace y with quote(2 * y) and not simply 2 * y. If you did the latter, then y would be evaluated in the standard way and would refer to the y in the enclosing scope, which is the parameter given to the call to f that creates g. Of course, that could be what you wanted: substitute whatever y is in the input expression with the y you have in the enclosing scope. In that case, the code would simply look like this:

f <- function(x, y) function(expr = x + y) {
  pi <- promise_info(expr)
  expr <- eval(substitute(
    substitute(expr, list(y = 2 * y))))
  value <- eval(expr, pi$env)
  list(expr = expr, value = value)
}
g <- f(2, 2)
g()
## $expr
## x + 4
##
## $value
## [1] 6
x <- y <- 4
g(x + y)
## $expr
## x + 4
##
## $value
## [1] 8
z <- 4
g(z)
## $expr
## z
##
## $value
## [1] 4

In both cases, you modify the expression in the promise—just in two different ways—and then you evaluate it in the promise scope. In the first case, y is taken from the promise scope if it appears in the modified expression; in the second case, y is replaced by the value you have in the enclosing scope.

Footnotes

1 In the package pryr, which you will return to at the end of this chapter, there are functions, written in C, that do provide access to the internals of promises. Using pryr, you can get hold of both the expression and the associated environment of a promise in case you need it.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.44.199