18.17 Example 2: Finding all variables created by assignment

  • Listing all the variables is a little more complicated.
  • Figure out what assignment looks like based on the AST.
ast(x <- 10)
# █─`<-`
# ├─x
# └─10
  • Now we need to decide what data structure we’re going to use for the results.
    • Easiest thing will be to return a character vector.
    • We would need to use a list if we wanted to return symbols.

18.17.1 Dealing with the base cases

find_assign_rec <- function(x) {
  switch_expr(x,
    constant = ,
    symbol = character()
  )
}
find_assign <- function(x) find_assign_rec(enexpr(x))

find_assign("x")
#> character(0)
find_assign(x)
#> character(0)

18.17.2 Dealing with the recursive cases

  • Here is the function to flatten pairlists.
flat_map_chr <- function(.x, .f, ...) {
  purrr::flatten_chr(purrr::map(.x, .f, ...))
}

flat_map_chr(letters[1:3], ~ rep(., sample(3, 1)))
#> [1] "a" "a" "b" "b" "c"
#> [1] "a" "b" "b" "b" "c" "c" "c"
  • Here is the code needed to identify calls.
find_assign_rec <- function(x) {
  switch_expr(x,
    # Base cases
    constant = ,
    symbol = character(),

    # Recursive cases
    pairlist = flat_map_chr(as.list(x), find_assign_rec),
    call = {
      if (is_call(x, "<-")) {
        as_string(x[[2]])
      } else {
        flat_map_chr(as.list(x), find_assign_rec)
      }
    }
  )
}

find_assign(a <- 1)
#> [1] "a"
find_assign({
  a <- 1
  {
    b <- 2
  }
})
#> [1] "a" "b"

18.17.3 Make the function more robust

  • Throw cases at it that we think might break the function.
  • Write a function to handle these cases.
find_assign_call <- function(x) {
  if (is_call(x, "<-") && is_symbol(x[[2]])) {
    lhs <- as_string(x[[2]])
    children <- as.list(x)[-1]
  } else {
    lhs <- character()
    children <- as.list(x)
  }

  c(lhs, flat_map_chr(children, find_assign_rec))
}

find_assign_rec <- function(x) {
  switch_expr(x,
    # Base cases
    constant = ,
    symbol = character(),

    # Recursive cases
    pairlist = flat_map_chr(x, find_assign_rec),
    call = find_assign_call(x)
  )
}

find_assign(a <- b <- c <- 1)
#> [1] "a" "b" "c"
find_assign(system.time(x <- print(y <- 5)))
#> [1] "x" "y"
  • This approach certainly is more complicated, but it’s important to start simple and move up.