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

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.

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: source: 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

Related

attaching packages to a "temporary" search path in R

Inside a function, I am sourcing a script:
f <- function(){
source("~/Desktop/sourceme.R") # source someone elses script
# do some stuff to the variables read in
}
f()
search() # library sourceme.R attaches is all the way in the back!
and unfortunately, the scripts that I am sourcing are not fully under my control. They make calls to library(somePackage), and it pollutes the search path.
This is mostly a problem if the author of sourceme.R expects the package that he/she is attaching to be at the top level/close to the global environment. If I myself have attached some package that masks some of the function names he/she is expecting to be available, then that's no good.
Is there a way I can source scripts but somehow make my own temporary search path that "resets" after the function is finished running?
I would consider sourcing the script in a separate R process using the callr package and then return the environment created by the sourced file.
By using a separate R process, this will prevent your search path from being polluted. I'm guessing there maybe some side effects (such as defining new functions of variables) in your global environment you do want. The local argument of the source functions allows you to specify where the parsed script should be executed. If you return this environment from the other R process, you can access any result you need.
Not sure what yours looks like but say I have this file that would modify the search path:
# messWithSearchPath.R
library(dplyr)
a <- data.frame(groupID = rep(1:3, 10), value = rnorm(30))
b <- a %>%
group_by(groupID) %>%
summarize(agg = sum(value))
From my top level script, I would write a wrapper function to source it in a new environment and have callr execute this function:
RogueScript <- function(){
rogueEnv <- new.env()
source("messWIthSearchPath.R", local = rogueEnv)
rogueEnv
}
before <- search()
scriptResults <- callr::r(RogueScript)
scriptResults$b
#> groupID agg
#> 1 1 -2.871642
#> 2 2 3.368499
#> 3 3 1.159509
identical(before, search())
#> [1] TRUE
If the scripts have other side effects (such as setting options or establishing external connections), this method probably won't work. There may be workarounds depending on what they are intended to do, but this should work if you just want the variables/functions created. It also prevents the scripts from conflicting with each other not just your top level script.
One way would be to "snapshot" your current search path and try to return to it later:
search.snapshot <- local({
.snap <- character(0)
function(restore = FALSE) {
if (restore) {
if (is.null(.snap)) {
return(character(0))
} else {
extras <- setdiff(search(), .snap)
# may not work if DLLs are loaded
for (pkg in extras) {
suppressWarnings(detach(pkg, character.only = TRUE, unload = TRUE))
}
return(extras)
}
} else .snap <<- search()
}
})
In action:
search.snapshot() # store current state
get(".snap", envir = environment(search.snapshot)) # view snapshot
# [1] ".GlobalEnv" "ESSR" "package:stats"
# [4] "package:graphics" "package:grDevices" "package:utils"
# [7] "package:datasets" "package:r2" "package:methods"
# [10] "Autoloads" "package:base"
library(ggplot2)
library(zoo)
# Attaching package: 'zoo'
# The following objects are masked from 'package:base':
# as.Date, as.Date.numeric
library(dplyr)
# Attaching package: 'dplyr'
# The following objects are masked from 'package:stats':
# filter, lag
# The following objects are masked from 'package:base':
# intersect, setdiff, setequal, union
search()
# [1] ".GlobalEnv" "package:dplyr" "package:zoo"
# [4] "package:ggplot2" "ESSR" "package:stats"
# [7] "package:graphics" "package:grDevices" "package:utils"
# [10] "package:datasets" "package:r2" "package:methods"
# [13] "Autoloads" "package:base"
search.snapshot(TRUE) # returns detached packages
# [1] "package:dplyr" "package:zoo" "package:ggplot2"
search()
# [1] ".GlobalEnv" "ESSR" "package:stats"
# [4] "package:graphics" "package:grDevices" "package:utils"
# [7] "package:datasets" "package:r2" "package:methods"
# [10] "Autoloads" "package:base"
I am somewhat confident (without verification) that this will not always work with all packages, perhaps due to dependencies and/or loaded DLLs. You can try adding force=TRUE to the detach call, not sure if that'll work better or perhaps have other undesirable side-effects.

Accessing current environment in R's eval function

According to this post, environment() function is the function to call a current environment.
However, I found that at least that is not the case in eval function, with following examples.
.env <- new.env()
.env$info$progress <- 3
.expr <- "environment()$info$progress <- 5"
eval(parse(text = .expr), envir = .env, enclos = .env)
> invalid (NULL) left side of assignment
I also tried assign function, but it does not work either
.env <- new.env()
.env$info$progress <- 3
.expr <- "assign(info$progress, 11, envir = environment())"
eval(parse(text = .expr), envir = .env, enclos = .env)
> Error in assign(info$progress, 11, envir = environment()) :
> invalid first argument
So environment function failed to find current environment in eval.
I would appreciate if anyone lets me know how to access current environment in above examples or how to move-around this issue in eval.
environment() does what you think it does. The issue is with assigning directly to the result of a function call.
> new.env()$info$progress <- 3
Error in new.env()$info$progress <- 3 :
invalid (NULL) left side of assignment
> .env <- new.env()
> .env$info$progress <- 3
> evalq(identical(environment(), .env), envir = .env)
[1] TRUE
> evalq({ e <- environment(); e$info$progress <- 5 }, envir = .env)
> .env$info
$progress
[1] 5
The goal (which I thought was access to a defined environment) can be accomplished by considering the fact that no call to environment is needed. That function with a NULL argument doesn't retrieve anything useful. The .env object is an environment, so the assignment should just be into it:
.env <- new.env()
.env$info$progres <- 3
.expr <- ".env$info$progres <- 5"
eval(parse(text = .expr) )
#------------
> ls(envir=.env)
[1] "info"
> ?get
> get("info", envir=.env)
$progres
[1] 5
The environment assignment operation is supposed to put values into the environment of functions. I think it's probably undefined when you make an assignment into an unbound environment. I would not have thought that environment()$info$progres <- 5 would have succeeded in placing a value into .env since the target of environment(.)<- was NULL.
Responding to your comment: I'm not sure what was meant by "a current environment". There is "the current environment" and the .env-environment was not that environment (nor was it ever that environment, even for an instant). Creating an environment with new.env does not make it the current environment. It only creates an environment which allows you to store or retrieve objects in it by referencing its name.
.env <- new.env()
environment()
#<environment: R_GlobalEnv>
It isn't even on the search path. It's kind of "on the sidelines" waiting to be referenced.
> search()
[1] ".GlobalEnv" "package:acs" "package:XML" "package:acepack" "package:abind"
[6] "package:downloader" "package:forcats" "package:stringr" "package:dplyr" "package:purrr"
[11] "package:readr" "package:tidyr" "package:tibble" "package:tidyverse" "tools:RGUI"
[16] "package:grDevices" "package:utils" "package:datasets" "package:graphics" "package:rms"
[21] "package:SparseM" "package:Hmisc" "package:ggplot2" "package:Formula" "package:stats"
[26] "package:survival" "package:sos" "package:brew" "package:lattice" "package:methods"
[31] "Autoloads" "package:base"
> ls(envir=.env)
[1] "info"
I find myself wondering if the goal was to use a more object-oriented style, and if so would recommend looking at the ?R6 help page and the section in the R Language Definition entitled: "5 Object-oriented programming".
After navigating through the help pages looking at the code for getAnywhere, ?find, ?ls, ?objects, I found a particular use of apropos that you might find interesting:
apropos("\\.", mode="environment")
[1] ".AutoloadEnv" ".BaseNamespaceEnv" ".env" ".GenericArgsEnv" ".GlobalEnv"
[6] ".userHooksEnv"
If you use:
apropos("." , mode="environment")`
..., constructed with the most generic pattern possible, you will also find the 100 or so ggproto-environments defined by ggplot2-functions, assuming you have that package loaded. I think Hadley's "Advanced Programming" may have more on this topic of interest because he defines a "environment list" class and functions to manipulate them.

R: Source personal scripts keeping some functions hidden

Follow up to this
I want to source scripts inside a given environment, like in sys.source, but "exporting" only some functions and keeping the others private.
I created this function:
source2=function(script){
ps=paste0(script, "_")
assign(ps, new.env(parent=baseenv()))
assign(script, new.env(parent=get(ps)))
private=function(f){
fn=deparse(substitute(f))
assign(fn, f, parent.env(parent.frame()))
rm(list=fn, envir=parent.frame())
}
assign("private", private, get(script))
sys.source(paste0(script, ".R"), envir=get(script))
rm(private, envir=get(script))
attach(get(script), name=script)
}
For the most part, this function works as expected.
Consider the script:
## foo.R
f=function() g()
g=function() print('hello')
private(g)
Note the private() function, which will hide g().
If I, so to say, import the module foo:
source2("foo")
I have a new environment in the search path:
search()
## [1] ".GlobalEnv" "foo" "package:stats"
## [4] "package:graphics" "package:grDevices" "package:utils"
## [7] "package:datasets" "package:methods" "Autoloads"
## [10] "package:base"
The current environment, .GlobalEnv, shows only:
ls()
## [1] "source2"
But if I list items in foo environment:
ls("foo")
## [1] "f"
Therefore I can run:
f()
## [1] "hello"
The problem is that g() is hidden totally.
getAnywhere(g)
## no object named 'g' was found
Too much. In fact, if I want to debug f():
debug(f)
f()
debugging in: f()
## Error in f() : could not find function "g"
The question is:
Where is g()? Can I still retrieve it?
Use:
get("g",env=environment(f))
## function ()
## print("hello")
## <environment: 0x0000000018780c30>
ls(parent.env(environment(f)))
## [1] "g"
Credit goes to Alexander Griffith for the solution.

Character Vector of loaded packages

I am currently trying to translate my loaded packages into a character vector to use in the pkgDep function. Does anyone have any idea on how to do this? Currently my results are formatted as a list, and using the unlist()function has not worked for me. I think rapply would do the trick, but I am running into issues on how to set up the function. I have pasted my code below. Thanks!
x <- loaded_packages()
typeof(x)
#need a character vector with package names to pass into function
pkgList <- pkgDep(x, availPkgs = pkgdata, suggests=TRUE)`
Use search() function to see the packages currently loaded.
x <- search()
x
# [1] ".GlobalEnv" "package:dplyr" "package:stats"
# [4] "package:graphics" "package:grDevices" "package:utils"
# [7] "package:datasets" "package:methods" "Autoloads"
# [10] "package:base"
pkgList <- pkgDep(x, availPkgs = pkgdata, suggests=TRUE)`
If you can tell us what pkgDep() function does, we can get the loaded packages list in specific format.
Try this function:
x <- search()
As per this link.

How to distinguish package namespace environment from other environment objects

Is there any way to programmatically distinguish between package environments and non-package environment objects? For example, the objects x and y below are both environments, with the same class and attributes.
x <- as.environment(cars)
y <- getNamespace("graphics")
However judging from the print method there is a difference:
> print(x)
<environment: 0x1d38118>
> print(y)
<environment: namespace:graphics>
Now suppose I have an arbitrary object, how can I determine which of the two it is (without looking at the output of print)? I would like to know this to determine how to store the object on disk. In case of the former I need to store the list representation of the environment (and perhaps its parents), but for the latter I would just store the name and version of the package.
isNamespace ?
isNamespace(y)
# [1] TRUE
isNamespace(x)
# [1] FALSE
And, for future reference, apropos is often helpful when you've got a question like this.
apropos("namespace")
# [1] "..getNamespace" ".BaseNamespaceEnv" ".getNamespace"
# [4] ".methodsNamespace" "asNamespace" "assignInMyNamespace"
# [7] "assignInNamespace" "attachNamespace" "fixInNamespace"
# [10] "getFromNamespace" "getNamespace" "getNamespaceExports"
# [13] "getNamespaceImports" "getNamespaceInfo" "getNamespaceName"
# [16] "getNamespaceUsers" "getNamespaceVersion" "isBaseNamespace"
# [19] "isNamespace" "loadedNamespaces" "loadingNamespaceInfo"
# [22] "loadNamespace" "namespaceExport" "namespaceImport"
# [25] "namespaceImportClasses" "namespaceImportFrom" "namespaceImportMethods"
# [28] "packageHasNamespace" "parseNamespaceFile" "requireNamespace"
# [31] "setNamespaceInfo" "unloadNamespace"

Resources