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
Related
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
I need to forward ... to a function argument, extract the symbols as code, and convert them to characters, all while preserving names. I usually use match.call(expand.dots = FALSE)$... for this, but it mangles the !! operator.
Current behavior
f <- function(...){
match.call(expand.dots = FALSE)$...
}
f(a = 1234, b = !!my_variable)
## $a
## [1] 1234
##
## $b
## !(!my_variable)
Desired behavior
f <- function(...){
# whatever works
}
f(a = 1234, b = !!my_variable)
## $a
## [1] 1234
##
## $b
## !!my_variable
EDIT Even better:
f <- function(...){
# whatever works
}
my_variable <- "my value"
f(a = 1234, b = !!my_variable)
## $a
## [1] 1234
##
## $b
## [1] "my_value"
The following code appears to work. The use case is here. Thanks to MrFlick for nudging me in the right direction.
f <- function(...){
rlang::exprs(...)
}
my_variable <- "my value"
f(a = 1234, b = !!my_variable)
## $a
## [1] 1234
##
## $b
## [1] "my_value"
EDIT: by the way, I am still looking for a way to parse !! without evaluating it. This would enhance user-side functionality related to https://github.com/ropensci/drake/issues/200.
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
By playing around with a function in R, I found out there are more aspects to it than meets the eye.
Consider ths simple function assignment, typed directly in the console:
f <- function(x)x^2
The usual "attributes" of f, in a broad sense, are (i) the list of formal arguments, (ii) the body expression and (iii) the environment that will be the enclosure of the function evaluation frame. They are accessible via:
> formals(f)
$x
> body(f)
x^2
> environment(f)
<environment: R_GlobalEnv>
Moreover, str returns more info attached to f:
> str(f)
function (x)
- attr(*, "srcref")=Class 'srcref' atomic [1:8] 1 6 1 19 6 19 1 1
.. ..- attr(*, "srcfile")=Classes 'srcfilecopy', 'srcfile' <environment: 0x00000000145a3cc8>
Let's try to reach them:
> attributes(f)
$srcref
function(x)x^2
This is being printed as a text, but it's stored as a numeric vector:
> c(attributes(f)$srcref)
[1] 1 6 1 19 6 19 1 1
And this object also has its own attributes:
> attributes(attributes(f)$srcref)
$srcfile
$class
[1] "srcref"
The first one is an environment, with 3 internal objects:
> mode(attributes(attributes(f)$srcref)$srcfile)
[1] "environment"
> ls(attributes(attributes(f)$srcref)$srcfile)
[1] "filename" "fixedNewlines" "lines"
> attributes(attributes(f)$srcref)$srcfile$filename
[1] ""
> attributes(attributes(f)$srcref)$srcfile$fixedNewlines
[1] TRUE
> attributes(attributes(f)$srcref)$srcfile$lines
[1] "f <- function(x)x^2" ""
There you are! This is the string used by R to print attributes(f)$srcref.
So the questions are:
Are there any other objects linked to f? If so, how to reach them?
If we strip f of its attributes, using attributes(f) <- NULL, it doesn't seem to affect the function. Are there any drawbacks of doing this?
As far as I know, srcref is the only attribute typically attached to S3 functions. (S4 functions are a different matter, and I wouldn't recommend messing with their sometimes numerous attributes).
The srcref attribute is used for things like enabling printing of comments included in a function's source code, and (for functions that have been sourced in from a file) for setting breakpoints by line number, using utils::findLineNum() and utils::setBreakpoint().
If you don't want your functions to carry such additional baggage, you can turn off recording of srcref by doing options(keep.source=FALSE). From ?options (which also documents the related keep.source.pkgs option):
‘keep.source’: When ‘TRUE’, the source code for functions (newly
defined or loaded) is stored internally allowing comments to
be kept in the right places. Retrieve the source by printing
or using ‘deparse(fn, control = "useSource")’.
Compare:
options(keep.source=TRUE)
f1 <- function(x) {
## This function is needlessly commented
x
}
options(keep.source=FALSE)
f2 <- function(x) {
## This one is too
x
}
length(attributes(f1))
# [1] 1
f1
# function(x) {
# ## This function is needlessly commented
# x
# }
length(attributes(f2))
# [1] 0
f2
# function (x)
# {
# x
# }
I jst figured out an attribute that compiled functions (package compiler) have that is not available with attributes or str. It's the bytecode.
Example:
require(compiler)
f <- function(x){ y <- 0; for(i in 1:length(x)) y <- y + x[i]; y }
g <- cmpfun(f)
The result is:
> print(f, useSource=FALSE)
function (x)
{
y <- 0
for (i in 1:length(x)) y <- y + x[i]
y
}
> print(g, useSource=FALSE)
function (x)
{
y <- 0
for (i in 1:length(x)) y <- y + x[i]
y
}
<bytecode: 0x0000000010eb29e0>
However, this doesn't show with normal commands:
> identical(f, g)
[1] TRUE
> identical(f, g, ignore.bytecode=FALSE)
[1] FALSE
> identical(body(f), body(g), ignore.bytecode=FALSE)
[1] TRUE
> identical(attributes(f), attributes(g), ignore.bytecode=FALSE)
[1] TRUE
It seems to be accessible only via .Internal(bodyCode(...)):
> .Internal(bodyCode(f))
{
y <- 0
for (i in 1:length(x)) y <- y + x[i]
y
}
> .Internal(bodyCode(g))
<bytecode: 0x0000000010eb29e0>
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.