When/How/Where Is Parent.Frame in a Default Argument Interpreted

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 evals 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



Leave a reply



Submit