Let

Nina Zumel, John Mount

2018-01-03

The vignette demonstrates the use of let to standardize calls to functions that use non-standard evaluation.

For the purposes of this discussion, standard evaluation of variables preserves referential transparency: that is, values and references to values behave the same.

x = 5
print(5 + 1)
## [1] 6
print(x + 1)
## [1] 6

Some functions in R use non-standard evaluation (NSE) of variables, in order to snoop variable names (for example, plot), or to delay or even avoid argument evaluation (for example library(foobar) versus library("foobar")).

In the case of plot, NSE lets plot use the variable names as the axis labels.

set.seed(1234)
xvar = runif(100) - 0.5
yvar = dnorm(xvar)

plot(xvar, yvar) 

In the case of library, non-standard evaluation saves typing a couple of quotes. The dollar sign notation for accessing data frame columns also uses non standard evaluation.

d <- data.frame(x=c(1,NA))
d$x
## [1]  1 NA

Issues arise when you want to use functions that use non-standard evaluation – for brevity, I’ll call these NSE expressions – but you don’t know the name of the variable, as might happen when you are calling these expression from within another function. Generally in these situations, you are taking the name of the desired variable from a string. But how do you pass it to the NSE expression?

# this fails if you forget to set character.only=TRUE
libname = "ggplot2"
tryCatch(
  library(libname),
  error = function(e) { print(e) })

# this fails referential transparency
xvariable = "xvar"
yvariable = "yvar"
tryCatch(
  plot(xvariable, yvariable),
  error = function(e) { print(e) })

Clearly, these examples are trivial, and one can find a workaround. There are other situations that are much more complex, for example trying to write parameterized dplyr transformations. See this blog post for an example of using let to parameterize dplyr expressions, and this post for a discussion of alternative solutions (as of this writing and version 0.5.0 of dplyr).

For this discussion, we will demonstrate let to standardize calling plot with unknown variables. let takes two arguments:

Here’s the plot example again.

library("wrapr")

xvariable = "xvar"
yvariable = "yvar"
let(
  list(X=xvariable, Y=yvariable),
  { # since we have the names as strings, we can create a title
    title = paste(yvariable, "vs", xvariable)
    plot(X, Y, main=title)
  }
)

Implementation details

Roughly wrapr::let(A, B) behaves like a syntatic sugar for eval(substitute(B, A)).

a <- 1
b <- 2
let(c(z=quote(a)), z+b)
## [1] 3
eval(substitute(z+b, c(z=quote(a))))
## [1] 3

However, wrapr::let() is actually implemented in terms of a de-parse and string substitution (to get around some issues of when and in what environment things are executed).

More

For more discussion please see: