rlang: The assignment form of quo, documentation, finding of. Or something

129 views Asked by At

A lot of the help page examples for quo take forms like this one, assigning an output of quo to quo:

quo <- quo(letters)
quo <- quo(toupper(!! quo))
quo
eval_tidy(quo)

It seems clear that these assignments do not overwrite the quo function (since he does it twice in a row) as they normally would. First-rate objects, and all that.

So my best guess of what is going on is that this is not a normal assignment, but rather the assignment form of quo, quo<-. But I have not been able to any information on this.

Unproductive:

getAnywhere(quo<-)
getAnywhere(`quo<-`)
getAnywhere(`quo <-`)
rlang:::quo<-
rlang:::`quo<-`
rlang:::`quo <-`

So I'd like someone who can tell me what the assignment form is for and how it works, and even more so if what I am seeing is not the assignment form, butt rather some aspect of the rather gnomic "[q]uo self-evaluates in its own environment."

Finally, if there is accessible documentation of this function or usage somewhere, I'd like to know how I could have found it, and if not, I should probably tell Hadley. How he keeps so many plates spinning without dropping more balls than he does is a complete mystery to me.

1

There are 1 answers

1
Aurèle On BEST ANSWER
library(rlang)

You've just attached rlang, your search path now looks like:

search()
#  [1] ".GlobalEnv"        "package:rlang"     "package:stats"    
#  [4] "package:graphics"  "package:grDevices" "package:utils"    
#  [7] "package:datasets"  "package:methods"   "Autoloads"        
# [10] "package:base"  

If you do this interactively at the command line, this should happen in .GlobalEnv:

quo <- quo(letters)

ls(.GlobalEnv)
# [1] "quo"

Now quo or get("quo") start the search for quo in .GlobalEnv, find it there (the quosure you've just created) and stop:

quo
get("quo")
get("quo", .GlobalEnv)
# <quosure: global>
# ~letters

If you want to find the function, you have to bypass .GlobalEnv and start the search in its enclosing environment, package:rlang. You're in luck, this is where it resides:

get("quo", as.environment("package:rlang"))
# function (expr) 
# {
#     enquo(expr)
# }
# <environment: namespace:rlang>

(Oh, this namespace:rlang you see is not where we've found quo, but its enclosing environment, viz. where it's going to start its own search when it's called. See the map of the world: http://blog.obeautifulcode.com/img/how-r-searches-and-finds-stuff/map-of-the-world.pngsource: Suraj Gupta http://blog.obeautifulcode.com/R/How-R-Searches-And-Finds-Stuff/)

> sapply(search(), function(x) "quo" %in% ls(x))
       .GlobalEnv     package:rlang     package:stats  package:graphics 
             TRUE              TRUE             FALSE             FALSE 
package:grDevices     package:utils  package:datasets   package:methods 
            FALSE             FALSE             FALSE             FALSE 
        Autoloads      package:base 
            FALSE             FALSE 

If you already know you're looking for a function, you can use the handy fget in pryr that overlooks everything that is not a function:

pryr::fget("quo")
# function (expr) 
# {
#     enquo(expr)
# }
# <environment: namespace:rlang>

(Note: match.fun can also come in handy when you're writing your own function).

Or you can just let R be smart, and directly use parens, R will know it has to look for a function, this is what is done in:

quo <- quo(toupper(!! quo))

(Also compare:

> bar
Error: object 'bar' not found
> bar()
Error in bar() : could not find function "bar"

The error messages hint at what R can't find/looks for, with and without parens.)


Now for fun, let's attach the whole tidyverse:

library(tidyverse)

Search path just got a lot messier:

search()
#  [1] ".GlobalEnv"        "package:forcats"   "package:stringr"  
#  [4] "package:dplyr"     "package:purrr"     "package:readr"    
#  [7] "package:tidyr"     "package:tibble"    "package:ggplot2"  
# [10] "package:tidyverse" "package:rlang"     "package:stats"    
# [13] "package:graphics"  "package:grDevices" "package:utils"    
# [16] "package:datasets"  "package:methods"   "Autoloads"        
# [19] "package:base" 

If we're curious and want to know where quo is found, we can use where from pryr. Again, we should bypass .GlobalEnv and start in its enclosing environment, package:forcats, for instance:

pryr::where("quo", env = "package:forcats")
# Using environment package:forcats
# <environment: package:dplyr>
# attr(,"name")
# [1] "package:dplyr"
# attr(,"path")
# [1] "/home/aurele/R/x86_64-pc-linux-gnu-library/3.4/dplyr"

Surprise, quo is now found in package:dplyr rather than package:rlang. This is because package:dplyr comes before in the search path, and as it happens, it re-exports quo from rlang to make it directly available in dplyr.

> sapply(search(), function(x) "quo" %in% ls(x))
       .GlobalEnv   package:forcats   package:stringr     package:dplyr 
             TRUE             FALSE             FALSE              TRUE 
    package:purrr     package:readr     package:tidyr    package:tibble 
            FALSE             FALSE             FALSE             FALSE 
  package:ggplot2 package:tidyverse     package:rlang     package:stats 
            FALSE             FALSE              TRUE             FALSE 
 package:graphics package:grDevices     package:utils  package:datasets 
            FALSE             FALSE             FALSE             FALSE 
  package:methods         Autoloads      package:base 
            FALSE             FALSE             FALSE