# 15 Evaluation

## 15.1 Introduction

1. Q: Carefully read the documentation for source(). What environment does it use by default? What if you supply local = TRUE? How do you provide a custom argument?

A: source() allows us to specify an evaluation environment. By default, the global environment is used. Using a local environment is possible, by setting local = FALSE. We may even provide a specific environment by passing an environment object.

# create temporary, sourcable R script
tmp_file <- tempfile()
writeLines("print(x)", tmp_file)

# prepare testing "probes"
x <- "global environment"

locate_evaluation <- function(file, local){
x <- "local environment"
source(file, local = local)
}

env2 <- rlang::env(x = "specified envirionment")

# where will source() be evaluated?
locate_evaluation(tmp_file, local = FALSE)
#> [1] "global environment"
locate_evaluation(tmp_file, local = TRUE)
#> [1] "local environment"
locate_evaluation(tmp_file, local = env2)
#> [1] "specified envirionment"
2. Q: Predict the results of the following lines of code:

eval(quote(eval(quote(eval(quote(2 + 2))))))
eval(eval(quote(eval(quote(eval(quote(2 + 2)))))))
quote(eval(quote(eval(quote(eval(quote(2 + 2))))))) 

A: You can see the output of the code above here:

#> [1] 4
#> [1] 4
#> eval(quote(eval(quote(eval(quote(2 + 2))))))

Generally what happens, is that 2 + 2 is first quoted as an expression and than evaluated to 4. When we nest calls to quote() and eval() more calls will be added to the AST, but the pattern of quoting and evaluating stays the same.

# pattern: evaluate a quoted expression
eval(quote(
eval(quote(
eval(quote(
2 + 2))
))
))
#> [1] 4

lobstr::ast(eval(quote(eval(quote(eval(quote(2 + 2)))))))
#> █─eval
#> └─█─quote
#>   └─█─eval
#>     └─█─quote
#>       └─█─eval
#>         └─█─quote
#>           └─█─+
#>             ├─2
#>             └─2

TODO (optional) Maybe explain a little better, in which order this call is evaluated, left to right or so?

When we wrap this expression in another eval() the 4 will be evaluated once more, but the result doesn’t change. When we quote it, we no evaluation takes place and we capture the expression instead. We could say: “An outside quote() always wins.”

3. Q: Write an equivalent to get() using sym() and eval_bare(). Write an equivalent to assign() using sym(), expr(), and eval_bare(). (Don’t worry about the multiple ways of choosing an environment that get() and assign() support; assume that the user supplies it explicitly.)

# name is a string
get2 <- function(name, env) {}
assign2 <- function(name, value, env) {}

A:

# name is a string
get2 <- function(name, env) {}
assign2 <- function(name, value, env) {}
4. Q: Modify source2() so it returns the result of every expression, not just the last one. Can you eliminate the for loop?

5. Q: The code generated by source2() lacks source references. Read the source code for sys.source() and the help for srcfilecopy(), then modify source2() to preserve source references. You can test your code by sourcing a function that contains a comment. If successful, when you look at the function, you’ll see the comment and not just the source code.

6. Q: We can make base::local() slightly easier to understand by spreading out over multiple lines:

local3 <- function(expr, envir = new.env()) {
call <- substitute(eval(quote(expr), envir))
eval(call, envir = parent.frame())
}

Explain how local() works in words. (Hint: you might want to print(call) to help understand what substitute() is doing, and read the documentation to remind yourself what environment new.env() will inherit from.)

## 15.2 Quosures

1. Q: Predict what evaluating each of the following quosures will return.

library(rlang)

q1 <- new_quosure(expr(x), env(x = 1))
q1
#> <quosure>
#>   expr: ^x
#>   env:  0x65bd460

q2 <- new_quosure(expr(x + !!q1), env(x = 10))
q2
#> <quosure>
#>   expr: ^x + (^x)
#>   env:  0x6d7a1e8

q3 <- new_quosure(expr(x + !!q2), env(x = 100))
q3
#> <quosure>
#>   expr: ^x + (^x + (^x))
#>   env:  0x701a688

A: Each quosure will be evaluated in it’s own environment. Hence we get:

eval_tidy(q1)
#> [1] 1
eval_tidy(q2)
#> [1] 11
eval_tidy(q3)
#> [1] 111
2. Q: Write a function enenv() that captures the environment associated with an argument.

A:

## 15.3 Tidy evaluation

1. Q: Improve subset2() to make it more like real base::subset():

• Drop rows where subset evaluates to NA
• Give a clear error message if subset doesn’t yield a logical vector
• What happens if subset yields a vector that’s not the same as the number of rows in data? What do you think should happen?
2. Q: The third argument in base::subset() allows you to select variables. It treats variable names as if they were positions. This allows you to do things like subset(mtcars, , -cyl) to drop the cylinder variable, or subset(mtcars, , disp:drat) to select all the variables between disp and drat. How does this work? I’ve made this easier to understand by extracting it out into its own function that uses tidy evaluation.

select <- function(df, vars) {
vars <- enexpr(vars)
var_pos <- set_names(as.list(seq_along(df)), names(df))

cols <- eval_tidy(vars, var_pos)
df[, cols, drop = FALSE]
}
select(mtcars, -cyl)
3. Q: Here’s an alternative implementation of arrange():

invoke <- function(fun, ...) do.call(fun, dots_list(...))
arrange3 <- function(.data, ..., .na.last = TRUE) {
args <- enquos(...)

ords <- purrr::map(args, eval_tidy, data = .data)
ord <- invoke(order, !!!ords, na.last = .na.last)

.data[ord, , drop = FALSE]
}

Describe the primary difference in approach compared to the function defined in the text.

One advantage of this approach is that you could check each element of ... to make sure that input is correct. What property should each element of ords have?

4. Q: Here’s an alternative implementation of subset2():

subset3 <- function(data, rows) {
eval_tidy(quo(data[!!enquo(rows), , drop = FALSE]), data = data)
}

Use intermediate variables to make the function easier to understand, then explain how this approach differs to the approach in the text.

5. Q: Implement a form of arrange() where you can request a variable to sorted in descending order using named arguments:

arrange(mtcars, cyl, desc = mpg, vs)

(Hint: The descreasing argument to order() will not help you. Instead, look at the definition of dplyr::desc(), and read the help for xtfrm().)

6. Q: Why do you not need to worry about ambiguous argument names with ... in arrange()? Why is it a good idea to use the . prefix anyway?

7. Q: What does transform() do? Read the documentation. How does it work? Read the source code for transform.data.frame(). What does substitute(list(...)) do?

8. Q: Use tidy evaluation to implement your own version of transform(). Extend it so that a calculation can refer to variables created by transform, i.e. make this work:

df <- data.frame(x = 1:3)
transform(df, x1 = x + 1, x2 = x1 + 1)
#> Error in eval(substitute(list(...)), _data, parent.frame()): object 'x1' not found
9. Q: What does with() do? How does it work? Read the source code for with.default(). What does within() do? How does it work? Read the source code for within.data.frame(). Why is the code so much more complex than with()?

10. Q: Implement a version of within.data.frame() that uses tidy evaluation. Read the documentation and make sure that you understand what within() does, then read the source code.

## 15.4 Wrapping quoting functions

1. Q: When model building, typically the response and data are relatively constant while you rapidly experiment with different predictors. Write a small wrapper that allows you to reduce duplication in this situation.

pred_mpg <- function(resp, ...) {

}
pred_mpg(~ disp)
pred_mpg(~ I(1 / disp))
pred_mpg(~ disp * cyl)
2. Q: Another way to way to write boot_lm() would be to include the boostrapping expression (data[sample(nrow(data), replace = TRUE), , drop = FALSE]) in the data argument. Implement that approach. What are the advantages? What are the disadvantages?

3. Q: To make these functions somewhat more robust, instead of always using the caller_env() we could capture a quosure, and then use its environment. However, if there are multiple arguments, they might be associated with different environments. Write a function that takes a list of quosures, and returns the common environment, if they have one, or otherwise throws an error.

4. Q: Write a function that takes a data frame and a list of formulas, fitting a linear model with each formula, generating a useful model call.

5. Q: Create a formula generation function that allows you to optionally supply a transformation function (e.g. log()) to the response or the predictors.

## 15.5 Old exercises

1. Q: Run this code in your head and predict what it will print. Confirm or refute your prediction by running the code in R.

f <- function(...) {
x <- "f"
g(f = x, ...)
}
g <- function(...) {
x <- "g"
h(g = x, ...)
}
h <- function(...) {
enquos(...)
}
x <- "top"

out <- f(top = x)
out
purrr::map_chr(out, eval_tidy)
2. Q: What happens if you use expr() instead of enexpr() inside of subset2()?