Search code examples
rdplyrtidyverselubridaterlang

What is the correct way to put quoted strings in rlang functions?


I am writing a fuction that I will use regularly to filter for dates and names in our database (and then to perform some monthly counting/calculations on them). I would like to know what is the correct way to insert and evaluate strings within rlang functions in this case?

Am I doing right by using quo to put strings into the fuction?

Example:

 business_flights = tibble(passanger_name=rep(c(rep("John RED",3),rep("Mary ORANGE",3)),4),
                              dep_date=seq(from = lubridate::ymd('2005-04-07'), 
                                           to = lubridate::ymd('2025-03-22'), length.out = 24),
                              flight_num = sample(seq(from = 99, to = 1999, by = 30), size = 24, replace = TRUE))

filter_flights = function(mytibble, name, date0, date1) {
  require(tidyverse); require(lubridate)
  flights_filtered = mytibble %>%
    filter(dep_date >= !!date0, dep_date < !!date1,
           grepl(!!name, passanger_name))
  View(flights_filtered)
}

filter_flights(mytibble = business_flights, 
               name = quo("RED"), 
               date0 = quo("2005-10-13"), 
               date1 = quo(today()))

Solution

  • Non-standard evaluation allows you to capture and manipulate expressions. In base R, this is primarily accomplished through the use of quote:

    quote(x)
    # x
    
    quote( a*log10(b+5) )
    # a * log10(b + 5)
    

    However, any captured expression that consists of a single literal is itself that literal:

    identical( quote(5), 5 )          # TRUE
    identical( quote("a"), "a" )      # TRUE
    identical( quote(FALSE), FALSE )  # TRUE
    identical( quote(5+5), 10 )       # FALSE, expression is not a single literal
    

    rlang::quo from tidyverse builds on this functionality by capturing an expression AND the environment that gave rise to that expression. Together, these define a quosure:

    quo(x)
    # <quosure>
    # expr: ^x
    # env:  global
    
    f <- function() {quo(x)}
    f()
    # <quosure>
    # expr: ^x
    # env:  0x55b7e61d5c80
    

    Keeping expressions next to their environments allows you to ensure that they are always evaluated in a consistent fashion, as they make their way through your code:

    x <- 5
    g <- function( myexpr ) {
      x <- 10
      eval_tidy( myexpr ) 
    }
    
    x                 # Evaluate expression directly in its environment
    # 5
    
    g( quote(x) )     # Evaluate expression inside g()
    # 10
    
    g( quo(x) )       # Evaluate expression in its env, while inside g()
    # 5
    

    However, when capturing a literal inside a quosure, quo assigns an empty environment to it:

    quo("x")
    # <quosure>
    # expr: ^"x"
    # env:  empty
    

    That is because the string "x" will always evaluate to "x", regardless of what environment it's evaluated in. Because of this, there is almost never a good reason to quo a string object (or any literal for that matter). It doesn't do anything and, as pointed out in the comments, your code will work fine without it.