When/how/where is parent.frame in a default argument interpreted?
Default arguments are evaluated within the evaluation frame of the function call, from which place parent.frame()
is the calling environment. envir
's value will thus be a pointer to the environment from which fn
was called.
Also, just try it out to see for yourself:
debug(fn)
fn()
# debugging in: fn()
# debug at #2: {
# }
Browse[2]> envir
# <environment: R_GlobalEnv>
Strange behavior in default argument enclos =parent.frame() of eval function
z
will be available inside the function since it's defined in .GlobalEnv.
Simply put,
name <- function(x) {
print(z)
}
z <- 5
name(z)
# [1] 5
So while a
is still unknown until eval(t, list(a=7))
, z
is already available. If z
is not defined inside name
, it will be looked for in .GlobalEnv. What might be counterintuitive is that (a+z)
is undefined unless you specify an environment for a
. But for z
, there is no need to do so.
eval: why does enclos = parent.frame() make a difference?
So, different parents. In the example that doesn't work parent.frame
is looking up from inside eval
into the internal environment of subset2
. In the working example, parent.frame
is looking up from inside subset3
, likely to your global where your df sits.
Example:
tester <- function() {
print(parent.frame())
}
tester() # Global
(function() {
tester()
})() # Anonymous function's internal env
R eval has misleading documentation for the case that the envir argument is list or pairlist
For this specific example, in the first case, the definition of f3
is:
f3
#function(){
# eval(quote(y), envir = list())
# }
#<environment: 0x02a41584>
The default enclos = parent.frame()
argument will be evaluated in the evaluation frame (i.e. "inside") of eval
-- the parent of eval
s current environment is the current environment()
of its wrapper function (f3
). And its wrapper function "remembers" where it was created and searches for the y
correctly to find it.
In the second case, f3
is defined as:
f3
#function(){
# eval(quote(y), envir = list(),
# enclos = parent.frame())
# }
#<environment: 0x02a4e5e4>
Here, enclos = parent.frame()
is evaluated in the evaluation environment()
of f3
and, upon calling f3()
, its parent is the .GlobalEnv
where there is no y
.
As a clearer example (yet equally nested as the example) we could consider:
f0 = function(e) print(e)
f1 = function(e = parent.frame()) f0(e)
fA = function() #returns a function
{
function()
{
print(parent.frame())
print(environment())
f1()
}
}
fB = function() # returns a function
{
function()
{
print(parent.frame())
print(environment())
f1(parent.frame())
}
}
And call:
fA()()
#<environment: R_GlobalEnv> #<- parent of `fA()`'s `environment()`
#<environment: 0x06ff25bc> #<- current env of `fA()`
#<environment: 0x06ff25bc> #<- parent of `f1` == current of `fA()`
fB()()
#<environment: R_GlobalEnv> #<- parent of `fA()`'s `environment()`
#<environment: 0x06fec304> #<- current env of `fA()`
#<environment: R_GlobalEnv> #<- parent is `eval`ed as parent of `fA()`'s current
As Taz notes, the distinction between default and supplied arguments is stated in the definition manual. Fooling a bit around we could see it in action by trying to reach the promises of arguments:
First, a helper function to access the promises of current arguments:
.ff = inline::cfunction(sig = c(symarg = "symbol", env = "environment", penv = "environment"), body = '
SEXP arg = findVar(symarg, env), ans = allocVector(VECSXP, 5);
SET_VECTOR_ELT(ans, 0, PRCODE(arg));
SET_VECTOR_ELT(ans, 1, PRENV(arg));
SET_VECTOR_ELT(ans, 2, eval(PRCODE(arg), PRENV(arg)));
SET_VECTOR_ELT(ans, 3, env);
SET_VECTOR_ELT(ans, 4, penv);
return(ans);
')
And the function whose arguments we 'll track:
ff = function(arg = parent.frame())
{
ans = setNames(.ff(quote(arg), environment(), parent.frame()),
c("expr", "envir", "val", "cur", "par"))
cat(sprintf("promise:\n\tcall: '%s'\n\tsearched at: '%s'\n\tfound as: '%s'\ncurrent: '%s'\nparent: '%s'\n%s\n",
deparse(ans$expr), capture.output(ans$envir),
capture.output(ans$val), capture.output(ans$cur),
capture.output(ans$par), strrep("-", 40)))
return(invisible(ans))
}
And a simple example:
ff()
#promise:
# call: 'parent.frame()'
# searched at: '<environment: 0x06fff594>'
# found as: '<environment: R_GlobalEnv>'
#current: '<environment: 0x06fff594>'
#parent: '<environment: R_GlobalEnv>'
#----------------------------------------
ff(parent.frame())
#promise:
# call: 'parent.frame()'
# searched at: '<environment: R_GlobalEnv>'
# found as: '<environment: R_GlobalEnv>'
#current: '<environment: 0x06fcce20>'
#parent: '<environment: R_GlobalEnv>'
#----------------------------------------
Or a more nested case:
fnest1 = function() ff()
fnest2 = function() ff(parent.frame())
fnest1()
#promise:
# call: 'parent.frame()'
# searched at: '<environment: 0x028d1ccc>'
# found as: '<environment: 0x028d1d20>'
#current: '<environment: 0x028d1ccc>'
#parent: '<environment: 0x028d1d20>'
#----------------------------------------
fnest2()
#promise:
# call: 'parent.frame()'
# searched at: '<environment: 0x026866b8>'
# found as: '<environment: R_GlobalEnv>'
#current: '<environment: 0x0268662c>'
#parent: '<environment: 0x026866b8>'
#----------------------------------------
Why parent.frame(5)?
Rather than using relative parent frames, it's much more direct to just capture the frame as the start of the function then pass that directly. (You don't have control over how many frames tryCatch
created when it runs).
And while I think allowing either a string or a symbol name is really just a dangerous mess, R does allow it. You can inspect the type of the promise passed to the function and deparse it if it's not a string. It's would be better to have two different parameters. One if you want to pass a symbol, and a different one if you want to pass a character.
loa <- function(x, dir='./dados/') {
toenv <- parent.frame()
xn <- substitute(x)
if (!is.character(xn)) xn <- deparse(xn)
if (right(dir,1) != '/') dir <- paste0(dir, '/')
path <- paste0(dir,gsub('\"', '', xn), '.rda')
tryCatch(load(path, envir = toenv),
error = function(e) print(e)
)
}
Purpose of this R idiom (match.call followed by eval(parent.frame())
I think this should answer everything, explanations are in the code :
# for later
FOO <- function(x) 1000 * x
y <- 1
foo <- function(...) {
cl = match.call()
message("cl")
print(cl)
message("as.list(cl)")
print(as.list(cl))
message("class(cl)")
print(class(cl))
# we can modify the call is if it were a list
cl[[1]] <- quote(FOO)
message("modified call")
print(cl)
y <- 2
# now I want to call it, if I call it here or in the parent.frame might
# give a different output
message("evaluate it locally")
print(eval(cl))
message("evaluate it in the parent environment")
print(eval(cl, parent.frame()))
message("eval.parent is equivalent and more idiomatic")
print(eval.parent(cl))
invisible(NULL)
}
foo(y)
# cl
# foo(y)
# as.list(cl)
# [[1]]
# foo
#
# [[2]]
# y
#
# class(cl)
# [1] "call"
# modified call
# FOO(y)
# evaluate it locally
# [1] 2000
# evaluate it in the parent environment
# [1] 1000
# eval.parent is equivalent and more idiomatic
# [1] 1000
getting the arguments of a parent function in R, with names
get_args <- function()
{
cl <- sys.call(-1)
f <- get(as.character(cl[[1]]), mode="function", sys.frame(-2))
cl <- match.call(definition=f, call=cl)
as.list(cl)[-1]
}
The key here is to set the definition
argument to match.call
to be get_arg
's calling function. This should (hopefully!) work for the general case where get_args
can be called from anywhere.
apply() and forceAndCall() ignoring get() from parent.frame()
The key issue here are the calling environments and the way get(inherits=TRUE)
handles them. For starters, I suggest one reads Hadley's Function environments.
More specifically, one has to understand function's enclosing and calling environments to understand what is going on.
v
is defined in the function f
calling environment, which is -- looking from the function's execution environment -- parent.frame(1L)
in the first print statement, and parent.frame(2L)
in the last two print statements.
I thought that if I supply envir=parent.frame(1L)
with inherits=TRUE
, the get
function would traverse the parent.frame()
hierarchy and eventually find v
in one of the parent calling environments. It does not. In reality it starts from the envir
environment one supplies and looks in the parent enclosing environments for v
. For the second print statement, the enclosing parent environment of parent.frame(1L)
is exactly where v
is defined, but in the last print statement, the enclosing parent environment of parent.frame(1L)
is the base
namespace where apply
is defined. Enclosing parent environment on top of that is the global environment. So v
is not found.
What we really need is a get
function that doesn't search in the parent enclosing environments hierarchy for v
, but in parent calling environments hierarchy. And that is exactly what dynget()
does.
dynGet() is somewhat experimental and to be used inside another
function. It looks for an object in the callers, i.e., the
sys.frame()s of the function. Use with caution
This function will traverse the parent calling environments sys.parents()
with sys.frame()
and look for v
in each of them. So f <- function(x) dynGet('v', inherits = TRUE)
solves the problem.
Is it possible to set a function's environment while runtime in R?
With this code you get exactly what you're looking for:
subfun0 <- function() {
e <- parent.frame()
attr(e, "name") <- "my_env"
assign("my_env", 1,
envir = parent.frame(),
inherits = FALSE, immediate = TRUE)
return(NULL)
}
subsubfun <- function() {
print(" subsubfun")
print(" =========")
print(exists("my_env"))
print(exists("my_env", parent.frame()))
print(exists("my_env", parent.frame(2)))
return(NULL)
}
subfun <- function() {
print(" subfun")
print(" ======")
print(exists("my_env"))
print(exists("my_env", parent.frame()))
print(exists("my_env", parent.frame(2)))
subsubfun()
return(NULL)
}
fun1 <- function() {
print("fun1")
print("====")
subfun0()
print(exists("my_env"))
print(exists("my_env", parent.frame()))
print(exists("my_env", parent.frame(2)))
subfun()
return(NULL)
}
fun1()
[1] "fun1"
[1] "===="
[1] TRUE
[1] FALSE
[1] FALSE
[1] " subfun"
[1] " ======"
[1] FALSE
[1] TRUE
[1] FALSE
[1] " subsubfun"
[1] " ========="
[1] FALSE
[1] FALSE
[1] TRUE
NULL
The point is that: parent.frame(2)
is not equal to parent.env(parent.frame())
Related Topics
How to Show Only The Lower Triangle in Ggpairs
Split Line by Multiple Points Using Sf Package
Ggplot2 Equivalent of 'Factorization or Categorization' in Googlevis in R
How to Extract Bold Text from a PDF Using R
How to Fix Degree Symbol Not Showing Correctly in R on Linux/Fedora 31
Function to Change Blanks to Na
Is There an Equivalent in Ggplot to The Varwidth Option in Plot
Reconstruct Symmetric Matrix from Values in Long-Form
Find Max Per Group and Return Another Column
Meaning of Error Using . Shorthand Inside Dplyr Function
Generating Split-Color Rectangles from Ggplot2 Geom_Raster()
Calculate Differences Between Rows Faster Than a for Loop
How to Get Column Names When Using Skip Along with Read.Csv
Filtering Single-Column Data Frames
Numerical Triple Integration in R
Creating a Table with Individual Trials from a Frequency Table in R (Inverse of Table Function)