I am trying to run a chunk of R code in a sandbox-ed fashion, by loading all the necessary dependencies (functions and data) into a new environment and evaluating an expression within that environment. However, I'm running into trouble with functions calling other functions in the environment. Here's a simple example:
jobenv <- new.env(parent=globalenv())
assign("f1", function(x) x*2, envir=jobenv)
assign("f2", function(y) f1(y) + 1, envir=jobenv)
expr <- quote(f2(3))
Using eval on expr fails since f2 can't find f1
> eval(expr, envir=jobenv)
Error in f2(3) : could not find function "f1"
whereas explicitly attaching the environment works
> attach(jobenv)
> eval(expr)
[1] 7
I'm probably missing something obvious, but I couldn't find any permutation of the eval call that works. Is there a way to get the same effect without attaching the environment?
There are a number of ways of doing this, but I kind of like this one:
jobenv <- new.env(parent=globalenv())
local({
f1 <- function(x) x*2
f2 <- function(y) f1(y) + 1
}, envir=jobenv)
## Check that it works
ls(jobenv)
# [1] "f1" "f2"
local(f2(3), envir=jobenv)
# [1] 7
eval(quote(f2(3)), envir=jobenv)
# [1] 7
Scope is defined when the function is created, not when it's called. See section 10.7 of the Introduction to R manual.
This seems a bit odd to me, but you get the same behavior even if you avoid assign all together and just use $<-.
jobenv <- new.env(parent=globalenv())
jobenv$f1 <- function(x) x*2
jobenv$f2 <- function(y) f1(y) + 1
expr <- quote(f2(3))
eval(expr, envir=jobenv)
This seems to be because the enclosing environment of f1 and f2 is the global environment. I would have expected it to be jobenv.
> environment(jobenv$f1)
<environment: R_GlobalEnv>
> environment(jobenv$f2)
<environment: R_GlobalEnv>
One solution is to explicitly set the environment of each function... but there has to be an easier way.
> environment(jobenv$f1) <- jobenv
> environment(jobenv$f2) <- jobenv
> eval(expr, envir=jobenv)
[1] 7
Related
In R, I can use .subset2 to act as a [[ or $ without dispatch.
> a <- new.env()
> a$foo <- 3
> .subset2(a, "foo")
[1] 3
However, I can't seem to find an equivalent for the setting operation without dispatch:
> .subset2(a, "foo") <- 5
Error in .subset2(a, "foo") <- 5 : could not find function
".subset2<-"
How can I set something without the implicit dispatching of using the [[<- or $<- operators?
You may be looking for assign:
Description: Assign a value to a name in an environment.
assign("foo", 5, envir = a)
By the way, instead of using .subset2 (which is an internal function in the Base Package and can be confused with subset), you might want to use get:
get("foo", envir = a)
# [1] 5
Maybe we can try
with(a,assign("foo",5))
or
with(a, `<-`(foo, 5))
or
a$foo <- 5
I'm not entirely sure this is what you're after, but it does meet the pre-reqs of setting the value for foo in the a environment without explicitly using the $, [, [[, <- operators:
# Your code:
a <- new.env()
a$foo <- 3
.subset2(a, "foo")
# Solution:
list2env(
list(
foo = 5
),
env = a
)
# Proof:
a$foo
I have run into quite a peculiar issue when trying to add a specific attribute to some of the functions in a R package I'm building.
Basically, I wish to add an attribute that includes all the classes the function is to be called on. This can either be specified manually or, and this is the tricky part, it can be done by looking up what methods are available for that function.
I can do this by writing something like
foo <- function(x) UseMethod("foo")
foo.character <- function(x) x
foo.numeric <- function(x) x + 1
makeAttributedFunction <- function(fName, classes = NULL) {
if (is.null(classes)) {
theseMethods <- as.character(methods(fName))
if (length(theseMethods) > 0) {
classes <- sub(paste(fName, ".", sep=""),
"", theseMethods)
} else classes <- character()
}
f <- get(fName)
attr(f, "classes") <- classes
class(f) <- "attributedFunction"
f
}
foo <- makeAttributedFunction("foo")
which works just fine when I run it directly in the console, then it get
> foo
function (x)
UseMethod("foo")
attr(,"classes")
[1] "character" "numeric"
attr(,"class")
[1] "attributedFunction"
However, if I do this in a script that is included in a package, makeAttributedFunction() no longer seems to be able to find the methods associated with foo(). Calling foo (which is in package fooPack) now returns
> fooPack:::foo
function (x)
UseMethod("foo")
<bytecode: 0x0000000019ec7100>
<environment: namespace:fooPack>
attr(,"classes")
character(0)
attr(,"class")
[1] "attributedFunction"
I have tried getting a glimpse of what is available in the environment while building a package by including a browser() line in makeAttributedFunction() and calling the check() function from devtools. This allows me to debug makeAttributedFunction(), but I'm not really certain it's in the "right" environment and it doesn't really help me. Here are some of the things I have tried:
Browse[1]> fName
[1] "foo"
Browse[1]> methods(fName)
no methods found
Browse[1]> get(fName)
function(x) UseMethod("foo")
<environment: namespace:fooPack>
Browse[1]> foo.character
function(x) x
<environment: namespace:fooPack>
Browse[1]> foo.numeric
function(x) x + 1
<environment: namespace:fooPack>
Browse[1]> isS3method("foo.numeric")
[1] TRUE
Browse[1]> parent.env(as.environment(-1L))
<environment: namespace:fooPack>
Browse[1]> ls(parent.env(as.environment(-1L))) #all objects in the namespace of fooPack
[15] "foo"
[16] "foo.character"
[17] "foo.numeric"
[18] "makeAttributedFunction"
Browse[1]> .S3methods("foo", envir = parent.env(as.environment(-1L)))
no methods found
Any ideas or pointers to what is going on here will be greatly appreciated - and either way, thanks for reading!
Why does the following not work?
f = function(...) for (i in ...) print(i)
f(1:3)
# Error in f(1:3) : '...' used in an incorrect context
while this work fine
f = function(...) for (i in 1:length(...)) print(...[i])
f(1:3)
# [1] 1
# [1] 2
# [1] 3
It does not work because the ... object type is not accessible in interpreted code. You need to capture the object as a list as nongkrong showed:
for(i in list(...))
Take a look at the R manual here
Here are 4 functions which mimics a family of functions from basic to advanced.
f1 <- function(expr,envir) {
eval(expr,envir)
}
f2 <- function(expr) {
expr <- substitute(expr)
f1(expr,parent.frame())
}
f3 <- function(x) {
lapply(1:3, function(i) {
f2(x+i)
})
}
f4 <- function(...) {
f3(...)
}
f1 is the fundamental one, f2 calls f1 with an expression, f3 iteratively calls f2 with x defined in its body frame.
Calling f3 yields
> f3(1)
[[1]]
[1] 2
[[2]]
[1] 3
[[3]]
[1] 4
which has no problem because the x is evaluated correctly in parent.frame() of f2. However, if a wrapper function f4 is called, an error occurs:
> f4()
Error in eval(expr, envir, enclos) :
argument "x" is missing, with no default
I inspect the parent environments local to f2 in line and find that parent.frame(3) contains x: missing.
Is there a way or good practice to manage the environments so that I can wrap functions like f4 without worrying too much about the parent environment issues?
I was wondering if there is anyway to get the environment of a declared variable. Say I already have declared a variable to an environment and want to use that variable's environment to declare a few more variables. Something like getEnv("variable")
Refer to:
http://adv-r.had.co.nz/Environments.html#env-basics
library(pryr)
x <- 5
where("x")
#> <environment: R_GlobalEnv>
where("mean")
#> <environment: base>
The where function is described in the above website. It only finds the first environment the variable appears in, but could easily be modified to find all.
You can get all objects in your workspace with ls(), so you can then check which of those are environments:
envirs <- ls()[sapply(ls(), function(x) is.environment(get(x)))]
I need to use get() there because ls() returns character names of objects rather than the objects themselves. Now given some object x, we want to find which environments it exists in. All we need to do is iterate through each environment in envirs, and check if they contain whatever object we're looking for. Something along the lines of (checking for a variable x):
sapply(envirs, function(e) 'x' %in% ls(envir=get(e)))
Here's a function to do all this:
getEnv <- function(x) {
xobj <- deparse(substitute(x))
gobjects <- ls(envir=.GlobalEnv)
envirs <- gobjects[sapply(gobjects, function(x) is.environment(get(x)))]
envirs <- c('.GlobalEnv', envirs)
xin <- sapply(envirs, function(e) xobj %in% ls(envir=get(e)))
envirs[xin]
}
This is more or less the same as what I did outside the function. gobjects reads from ls(), this time explicitly checking the global environment .GlobalEnv, since it is now within a function.
envirs is the same as before, except now it will check .GlobalEnv as well. xin is storing the names of which environments x was found in. The line:
xobj <- deparse(substitute(x))
Allows object to be tested without quotes e.g. getEnv(x) versus getEnv('x'). That's a matter of preference though, you can change it to accept characters instead.
Here's a few tests.
x1 <- 1
getEnv(x1)
# ".GlobalEnv"
x2 <- 2.1
e2 <- new.env()
assign('x2', 2.2, e2)
getEnv(x2)
# ".GlobalEnv" "e2"
e3 <- new.env()
assign('x3', 3, e3)
getEnv(x3)
# "e3"
This only checks environments created within .GlobalEnv. I'm sure you can work out how to extend it to search across more environments though if you need.
I'm surprised there isn't some in-built function for this. Or maybe there is and I don't know about it. I've never actually needed to do anything like this before so maybe it's not actually surprising.
How about this:
getEnvOf <- function(what, which=rev(sys.parents())) {
for (frame in which)
if (exists(what, frame=frame, inherits=FALSE))
return(sys.frame(frame))
return(NULL)
}
Then we can:
x <- 1
getEnvOf("x")
# <environment: R_GlobalEnv>
getEnvOf("y")
# NULL
f <- function() getEnvOf("x")
f()
# <environment: R_GlobalEnv>
g <- function() { x <- 2; getEnvOf("x") }
g()
# <environment: 0x114c26518>
You can use find, as already suggested in the notes, to search the environment of an object in the searchpath. In case you want to seach in the Function Call Stack you can use exists to look in the sys.frame's.
findFrame <- function(what) {
n <- sys.nframe()-1
Filter(Negate(is.null), lapply(n:0, function(i) {
if(exists(what, sys.frame(i), inherits=FALSE)) sys.frame(i)}))
}
x <- 0
f1 <- function() {
x <- 1
f2()
}
f2 <- function() {
x <- 2
tt <- find("x")
print(sapply(tt, as.environment))
tt <- findFrame("x")
print(tt)
}
a <- new.env(parent=emptyenv())
a$x <- 3
attach(a)
b <- new.env(parent=emptyenv())
b$x <- 4
find("x")
#[1] ".GlobalEnv" "a"
findFrame("x")
#[[1]]
#<environment: R_GlobalEnv>
f1()
#$.GlobalEnv
#<environment: R_GlobalEnv>
#
#$a
#<environment: 0x5576b0c7bb80>
#attr(,"name")
#[1] "a"
#
#[[1]]
#<environment: 0x5576af2fa1f8>
#
#[[2]]
#<environment: 0x5576aedcab20>
#
#[[3]]
#<environment: R_GlobalEnv>
f2()
#$.GlobalEnv
#<environment: R_GlobalEnv>
#
#$a
#<environment: 0x5576b0c7bb80>
#attr(,"name")
#[1] "a"
#
#[[1]]
#<environment: 0x5576b013bef0>
#
#[[2]]
#<environment: R_GlobalEnv>