Using R, how to get the environment object from the environment string? - r

Let's say I create a function:
x = function() { print(environmentName(environment())); envir=environment(); print(envir); print(environmentName(envir)); print(environmentName(environment())); print(environmentName(as.environment(environment()))); print(environmentName(parent.env(environment()))); envir; }
On my computer it prints out a unique hash of the environment in only one instance. That leads to a fundamental question about what is an envir and how the function environmentName(envir) actually works.
For example,
( env = x() );
outputs:
> ( env = x() );
[1] ""
<environment: 0x000001fec97c3eb0>
[1] ""
[1] ""
[1] ""
[1] "R_GlobalEnv"
<environment: 0x000001fec97c3eb0>
and reports an "" EMPTY environment name:
> environmentName(env)
[1] ""
When I was expecting a string "0x000001fec97c3eb0" or something. I guess every time I call the function x I get a new environment.
> environmentName(x)
[1] ""
> environmentName(environment(x))
[1] "R_GlobalEnv"
New environments
Maybe unless it is "FIXED" to a package or attached as an environment?
From (How to get environment of a variable in R)
a <- new.env(parent=emptyenv())
a$x <- 3
attach(a)
b <- new.env(parent=emptyenv())
b$x <- 4
This yields:
environmentName(a);
environmentName(b);
both EMPTY "" ???
It does seem to work on the following, called from GLOBAL:
> environmentName(environment());
[1] "R_GlobalEnv"
So there appears to be a fundamental question:
Fundament Question: How exactly does environmentName work?
But that was not my primary question. The above example is a function that has an envir as an input and returns a string. How can I reverse that process. That is, if I have a stringname R_GlobalEnv how do I return the envir R object?
Primary Question: Is there a function that reverses the logic of environmentName?
That is, if I have a stringname of the environmentName, what function do I call to reverse it? Or what function can be written to enhance the R experience?
e.g.,
env.toName = function(envir=environment()) { environmentName(envir); }
env.fromName = function(envirstr) { XXVXX(envirstr); }

Not all environments have names, which is why you see "" sometimes. environmentName(env) just returns the "name" attribute of env.
There is no standard function that can take the name and return the environment: to write one, you'd have to search all variables in view for the environments, then individually check for a name on each. You might find more than one with the same name attribute, e.g.
a <- new.env()
attr(a, "name") <- "myenv"
b <- new.env()
attr(b, "name") <- "myenv"
environmentName(a)
#> [1] "myenv"
environmentName(b)
#> [1] "myenv"
Created on 2022-09-17 with reprex v2.0.2
Names on environments are there just as decoration, not for unique identification. Check for equality using identical():
d <- a
environmentName(d)
#> [1] "myenv"
identical(a, b)
#> [1] FALSE
identical(a, d)
#> [1] TRUE

Related

Is there something like print method for environment in R?

I have created a simple environment in R with two simple function,
AnEnv <- new.env()
AnEnv$mod <- function(a, b) a%%b
AnEnv$pwr <- function(a, b) a^b
Whenever I type AnEnv in R-console, it returns something like <environment: 0x7f7f6fe3d4f0>. Is there a why this behaviour can be changed. For example, when I type AnEnv it returns the results from ls(env = AnEnv) or ls.str(env = AnEnv).
R already has ls(e), ls(e, all = TRUE), as.list(e) and str(as.list(e)) which will display the object names (except those that begin with dot), all object names, the entire contents of environment e and a summary of the contents so it is not clear what use this would be; however, we could add a name to an environment and then it will be displayed.
e <- new.env()
attr(e, "name") <- "my env"
e
## <environment: 0x000000001532f8b0>
## attr(,"name")
## [1] "my env"
or use environmentName:
environmentName(e)
## [1] "my env"
In some cases the environment already has a name and/or other attributes.
baseenv()
## <environment: base>
globalenv()
## <environment: R_GlobalEnv>
as.environment("package:graphics")
## <environment: package:graphics>
## attr(,"name")
## [1] "package:graphics"
## attr(,"path")
## [1] "C:/Program Files/R/R-4.1/library/graphics"
Perhaps you want something like this:
print.environment = function(x) {
for (obj in ls(envir=x)) {
cat(paste0(obj, ": "))
print(get(obj, envir=x))
}
}
print (AnEnv)
# mod: function(a, b) a%%b
# pwr: function(a, b) a^b

Accessing the function that is being called from within the function

Within a function, how can we reliably return an object that contains the function itself?
For example with:
functionBuilder <- function(wordToSay) {
function(otherWordToSay) {
print(wordToSay)
print(otherWordToSay)
get(as.character(match.call()[[1]]))
}
}
I can build a function like so:
functionToRun <- functionBuilder("hello nested world")
... and run it ...
functionToRun("A")
#[1] "hello nested world"
#[1] "A"
#
#function(otherWordToSay) {
# print(wordToSay)
# print(otherWordToSay)
# get(as.character(match.call()[[1]]))
# }
#<environment: 0x1e313678>
... as you can see functionToRun returns itself. However, this approach appears to break if I call functionToRun via sapply:
> sapply(LETTERS, functionToRun)
#[1] "hello nested world"
#[1] "A"
#Error in get(as.character(match.call()[[1]])) : object 'FUN' not found
I can see that this is because the actual call when using sapply is FUN but that FUN doesn't exist at pos = -1 (the default for get). Code that works in that position looks like:
get(as.character(match.call()[[1]]),envir = sys.frame(sys.parent()))
But that same code fails if the function hasn't been called via sapply because sys.frame(sys.parent())) goes too far back and ends up referring to R_GlobalEnv.
From the documentation (R 3.2.2) I'd have expected dynGet to perhaps solve the issue of only going as far back in the stack as needed. Although this works for an sapply call of the function, it fails when the function is called on its own. (Besides, it is marked as 'somewhat experimental'). Inversely getAnywhere seems promising, but doesn't seem to work for the sapply called function.
Is there a reliable way to return the function that is currently being processed, i.e. works for both a bare and sapply wrapped function call?
What I'm doing right now is wrapping the attempt to grab the function in a tryCatch; but I'm a little uncertain whether I can trust that get(as.character(match.call()[[1]]),envir = sys.frame(sys.parent())) will work in all wrapping cases (not just sapply). So, I'm looking for a more reasonable way to approach this problem.
Potentially Related Questions:
How to access a variable stored in a function in R
How to get the name of the calling function inside the called routine?
I can't guarantee that this will work in all cases, but it looks okay:
fun <- function(x) {
print(x)
y <- exp(x)
print(y)
sys.function(0)
}
fun(1)
# [1] 1
# [1] 2.718282
# function(x) {
# print(x)
# y <- exp(x)
# print(y)
# sys.function(0)
# }
lapply(1:5, fun)[[3]]
# [1] 1
# [1] 2.718282
# [1] 2
# [1] 7.389056
# [1] 3
# [1] 20.08554
# [1] 4
# [1] 54.59815
# [1] 5
# [1] 148.4132
# function(x) {
# print(x)
# y <- exp(x)
# print(y)
# sys.function(0)
# }
Of course, I don't understand what you need this for.

Checking for existence of a variable

I am really struggling to understand the following behaviour of R. Let's say we want to define a function f, which is supposed to return whether its argument exists as a variable; but we want to pass the argument without quotes. So for example to check whether variable y exists, we would call f(y).
f <- function(x) {
xchar <- deparse(substitute(x))
exists(xchar)
}
So I start a brand new R session and define f, but no other variables. I then get
f(y)
# [1] FALSE
f(z)
# [1] FALSE
f(f)
# [1] TRUE
f(x)
# [1] TRUE
The first three calls (on y, z, f) give the expected result. But there is no variable named x
exists("x")
# [1] FALSE
EDIT I now realise that this is because of the use of substitute, which will create the variable x. But is there a way around this?
The object x does exist inside the function since it is the name of the parameter.
If you modify the function
f <- function(...) {
xchar <- deparse(substitute(...))
exists(xchar)
}
you can see the expected output:
f(x)
# FALSE
You may want to just search the global environment
f <- function(x) {
xchar <- deparse(substitute(x))
exists(xchar,where=globalenv())
}
in which case you get:
> f(y)
[1] FALSE
> f(f)
[1] TRUE
> f(x)
[1] FALSE
> f(z)
[1] FALSE
> f(mean)
[1] TRUE

In R, how to get an object's name after it is sent to a function?

I am looking for the reverse of get().
Given an object name, I wish to have the character string representing that object extracted directly from the object.
Trivial example with foo being the placeholder for the function I am looking for.
z <- data.frame(x=1:10, y=1:10)
test <- function(a){
mean.x <- mean(a$x)
print(foo(a))
return(mean.x)}
test(z)
Would print:
"z"
My work around, which is harder to implement in my current problem is:
test <- function(a="z"){
mean.x <- mean(get(a)$x)
print(a)
return(mean.x)}
test("z")
The old deparse-substitute trick:
a<-data.frame(x=1:10,y=1:10)
test<-function(z){
mean.x<-mean(z$x)
nm <-deparse(substitute(z))
print(nm)
return(mean.x)}
test(a)
#[1] "a" ... this is the side-effect of the print() call
# ... you could have done something useful with that character value
#[1] 5.5 ... this is the result of the function call
Edit: Ran it with the new test-object
Note: this will not succeed inside a local function when a set of list items are passed from the first argument to lapply (and it also fails when an object is passed from a list given to a for-loop.) You would be able to extract the ".Names"-attribute and the order of processing from the structure result, if it were a named vector that were being processed.
> lapply( list(a=4,b=5), function(x) {nm <- deparse(substitute(x)); strsplit(nm, '\\[')} )
$a # This "a" and the next one in the print output are put in after processing
$a[[1]]
[1] "X" "" "1L]]" # Notice that there was no "a"
$b
$b[[1]]
[1] "X" "" "2L]]"
> lapply( c(a=4,b=5), function(x) {nm <- deparse(substitute(x)); strsplit(nm, '\\[')} )
$a
$a[[1]] # but it's theoretically possible to extract when its an atomic vector
[1] "structure(c(4, 5), .Names = c(\"a\", \"b\"))" ""
[3] "1L]]"
$b
$b[[1]]
[1] "structure(c(4, 5), .Names = c(\"a\", \"b\"))" ""
[3] "2L]]"
deparse(quote(var))
My intuitive understanding
In which the quote freeze the var or expression from evaluation
and the deparse function which is the inverse of parse function makes that freezed symbol back to String
Note that for print methods the behavior can be different.
print.foo=function(x){ print(deparse(substitute(x))) }
test = list(a=1, b=2)
class(test)="foo"
#this shows "test" as expected
print(test)
#this (just typing 'test' on the R command line)
test
#shows
#"structure(list(a = 1, b = 2), .Names = c(\"a\", \"b\"), class = \"foo\")"
Other comments I've seen on forums suggests that the last behavior is unavoidable. This is unfortunate if you are writing print methods for packages.
To elaborate on Eli Holmes' answer:
myfunc works beautifully
I was tempted to call it within another function (as discussed in his Aug 15, '20 comment)
Fail
Within a function, coded directly (rather than called from an external function), the deparse(substitute() trick works well.
This is all implicit in his answer, but for the benefit of peeps with my degree of obliviousness, I wanted to spell it out.
an_object <- mtcars
myfunc <- function(x) deparse(substitute(x))
myfunc(an_object)
#> [1] "an_object"
# called within another function
wrapper <- function(x){
myfunc(x)
}
wrapper(an_object)
#> [1] "x"

Assigning list attributes in an environment

The title is the self-contained question. An example clarifies it: Consider
x=list(a=1, b="name")
f <- function(){
assign('y[["d"]]', FALSE, parent.frame() )
}
g <- function(y) {f(); print(y)}
g(x)
$a
[1] 1
$b
[1] "name"
whereas I would like to get
g(x)
$a
[1] 1
$b
[1] "name"
$d
[1] FALSE
A few remarks. I knew what is wrong in my original example, but am using it to make clear my objective. I want to avoid <<-, and want x to be changed in the parent frame.
I think my understanding of environments is primitive, and any references are appreciated.
The first argument to assign must be a variable name, not the character representation of an expression. Try replacing f with:
f <- function() with(parent.frame(), y$d <- FALSE)
Note that a, b and d are list components, not list attributes. If we wanted to add an attribute "d" to y in f's parent frame we would do this:
f <- function() with(parent.frame(), attr(y, "d") <- FALSE)
Also, note that depending on what you want to do it may (or may not) be better to have x be an environment or a proto object (from the proto package).
assign's first argument needs to be an object name. Your use of assign is basically the same as the counter-example at the end of the the assign help page. Observe:
> x=list(a=1, b="name")
> f <- function(){
+ assign('x["d"]', FALSE, parent.frame() )
+ }
> g <- function(y) {f(); print(`x["d"]`)}
> g(x)
[1] FALSE # a variable with the name `x["d"]` was created
This may be where you want to use "<<-" but it's generally considered suspect.
> f <- function(){
+ x$d <<- FALSE
+ }
> g <- function(y) {f(); print(y)}
> g(x)
$a
[1] 1
$b
[1] "name"
$d
[1] FALSE
A further thought, offered in the absence of any goal for this exercise and ignoring the term "attributes" which Gabor has pointed out has a specific meaning in R, but may not have been your goal. If all you want is the output to match your specs then this achieves that goal but take notice that no alteration of x in the global environment is occurring.
> f <- function(){
+ assign('y', c(x, d=FALSE), parent.frame() )
+ }
> g <- function(y) {f(); print(y)}
> g(x)
$a
[1] 1
$b
[1] "name"
$d
[1] FALSE
> x # `x` is unchanged
$a
[1] 1
$b
[1] "name"
The parent.frame for f is what might be called the "interior of g but the alteration does not propagate out to the global environment.

Resources