2

I was reading the book Advanced R and confused with the concept of "escape hatch" repeatedly mentioned in the chapter on Non-standard evaluation. For example, the first time the author mentioned this word, it has the following definition:

As a developer, you should always provide an escape hatch: an alternative version of the function that uses standard evaluation.

It also has some examples about the escape hatch. One of the example is from the part Calling from another function. The author said:

Typically, computing on the language is most useful when functions are called directly by users and less useful when they are called by other functions.

See the code of the example below:

sample_df <- data.frame(a = 1:5, b = 5:1, c = c(5, 3, 1, 4, 1))

subset2 <- function(x, condition) { 
condition_call <- substitute(condition)
  r <- eval(condition_call, x, parent.frame())
  x[r, ]
}

scramble <- function(x) x[sample(nrow(x)), ]

subscramble <- function(x, condition) {
  scramble(subset2(x, condition))
}

But it doesn’t work:

subscramble(sample_df, a >= 4)
# Error in eval(expr, envir, enclos) : object 'a' not found
traceback()
#> 5: eval(expr, envir, enclos)
#> 4: eval(condition_call, x, parent.frame()) at #3
#> 3: subset2(x, condition) at #1
#> 2: scramble(subset2(x, condition)) at #2
#> 1: subscramble(sample_df, a >= 4)

The author said we could write a version of subset2() that takes an already quoted expression in this case. Codes are showed below:

subset2_q <- function(x, condition) {
  r <- eval(condition, x, parent.frame())
  x[r, ]
}
subset2 <- function(x, condition) {
  subset2_q(x, substitute(condition))
}

subscramble <- function(x, condition) {
  condition <- substitute(condition)
  scramble(subset2_q(x, condition))
}

Then it runs well:

subscramble(sample_df, a >= 3)
#>   a b c
#> 4 4 2 4
#> 5 5 1 1
#> 3 3 3 1
subscramble(sample_df, a >= 3)
#>   a b c
#> 5 5 1 1
#> 3 3 3 1
#> 4 4 2 4

Even though the author gives me the example, I still do not understand the escape hatch. So, can someone explain its definition in this book or in R programming language? My sessionInfo:

sessionInfo()
R version 3.5.0 (2018-04-23)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows 7 x64 (build 7601) Service Pack 1

Matrix products: default

locale:
[1] LC_COLLATE=Chinese (Simplified)_People's Republic of China.936 
[2] LC_CTYPE=Chinese (Simplified)_People's Republic of China.936   
[3] LC_MONETARY=Chinese (Simplified)_People's Republic of China.936
[4] LC_NUMERIC=C                                                   
[5] LC_TIME=Chinese (Simplified)_People's Republic of China.936    

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

loaded via a namespace (and not attached):
[1] compiler_3.5.0 tools_3.5.0    yaml_2.2.0
Leon Smith
  • 97
  • 6
  • The first function works for me. Could you post your sessionInfo? As for escape hatch, I think the author uses it as a term similar to its English definition(escape from an emergency situation) ie a function that will work if the one that uses NSE ever fails. – NelsonGon Sep 14 '19 at 14:41
  • I think you missed out something important to add more context to your question. In the chapter, the function only fails if the condition is stored as a variable to which the author proposes subset2_q as a solution. I suggest looking at `enquo(s)`, might be a better way to understand what's going on. – NelsonGon Sep 14 '19 at 14:56
  • I added the sessionInfo of my R engine and supply some context about escape hatch. Actually, the first code cannot run well. What's more, I cannot find a function named `enquo` or `enquos` in my R engine. Do you mean `enquote`? – Leon Smith Sep 15 '19 at 02:54
  • `enquo` is part of `rlang` and is often used for NSE dependent operations in such packages as `dplyr`. Not really sure why your code cannot run as it runs fine for me(at least it did when I ran it). I however am using R 3.6.1 so perhaps there were changes to `substitute`(not entirely sure). There is however no mention of [such a change](https://cran.r-project.org/bin/windows/base/NEWS.R-3.6.1.html). – NelsonGon Sep 15 '19 at 04:08
  • 1
    Finally I found enquo() function in rlang package. The reason why I did not find it before is that I did not attach rlang package. But amazingly I found even in R version 3.6.1, the first code can not run well. Some of the R sessionInfo: `R version 3.6.1 (2019-07-05) Platform: x86_64-pc-linux-gnu (64-bit) Running under: Ubuntu 16.04.3 LTS Matrix products: default BLAS: /usr/lib/libblas/libblas.so.3.6.0 LAPACK: /usr/lib/lapack/liblapack.so.3.6.0` – Leon Smith Sep 15 '19 at 05:50
  • The packages that are attached is: `attached base packages: \n [1] stats graphics grDevices utils datasets methods base \n other attached packages: \n [1] rlang_0.4.0 \n loaded via a namespace (and not attached): \n [1] compiler_3.6.1 tools_3.6.1 ` – Leon Smith Sep 15 '19 at 05:53

1 Answers1

0

The chapter on non-standard-evaluation (NSE) in Advanced R is a bit on the older side at this point. The "escape hatch" it refers to is as defined in your question: "an alternative version of the function that uses standard evaluation". If you look through the history of dplyr, you will notice functions like select_ and filter_, which are effectively standard-evaluation (SE) versions of select and filter. These functions have since been deprecated and the entire concept of maintaining two versions of functions (one SE and one NSE) has also fallen out of favor. The modern approach to NSE and its usage in dplyr is instead covered in the more recent book on tidy evaluation.

The reason your code is not working is because substitute() does not function properly in nested functions. In your case:

## Works as intended
scramble(subset2( sample_df, a >= 4 ))

## Does essentially the same thing but placed within a function
## Fails due to substitute() being inside a now-nested function
subscramble( sample_df, a >= 4 )

To fix the functions, you can simply switch from substitute() to rlang::enexpr() and use quasiquotation to pass the condition expression throughout nested functions. Note that you don't need enquo() in this case; enquo() is a version of enexpr() that also captures the environment where the expression is to be evaluated. In your case, that environment is the data frame, which is being passed to the functions as x already.

subset2 <- function(x, condition) {
  condition_call <- rlang::enexpr(condition)
  r <- eval(condition_call, x, parent.frame())
  x[r, ]
}

subscramble <- function(x, condition) {
  scramble(subset2(x, !!rlang::enexpr(condition)))
}

subscramble( sample_df, a >= 4 )   ## Now works as intended
Artem Sokolov
  • 13,196
  • 4
  • 43
  • 74