0

In the following example, I try to get an element from a grandparent environment using env_get. The first bit works as expected.

library(rlang)
e1 <- env(a = 'a')

# works as expected
f <- function() {
  env_get(
    env = caller_env(), 
    nm = 'a', 
    inherit = TRUE, 
    default = 'not found')
}
exec(f, .env = e1)
#> [1] "a"

# two levels deep of function calls doesn't work even though inherit = TRUE
g <- function() f()
exec(g, .env=e1)
#> [1] "not found"


# modifying the depth of caller_env in f does work
f <- function() {
  env_get(
    env = caller_env(2), # <------ changing this
    nm = 'a', 
    inherit = TRUE, 
    default = 'not found')
}
exec(g, .env=e1)
#> [1] "a"

Created on 2021-12-28 by the reprex package (v2.0.1)

I expected that the second bit, calling exec on g with .env=e1 would work, as the call to env_get has inherit=TRUE. My understanding was that it would look in the caller_env, find nothing, and look in its parent to find "a", but this did not work. Further confusing me on this is what when I explicitly instruct env_get to look 2 levels up, this does work.

Am I misunderstanding something about how this inheritance should work?

crf
  • 1,810
  • 3
  • 15
  • 23

1 Answers1

0

You are confused over two concepts:

  • Calling environment: where the function is called. This is the return value of caller_env().
  • Execution/Evaluation environment: where the object/function is evaluated. This is the return value of current_env().

When you exec a function in an environment e1, the function would be called in e1, but its arguments and the procedures written in its body would be evaluated in an isolated environment, that is, current_env(). Usually, current_env() exists only temporarily and will be destroyed upon completion of the function evaluation. Another important thing is that the parent envionment of current_env() is not caller_env() but where the function is created. For instance,

e1 <- new.env()
h <- function() {
  print(c(
    h_evaluated_in = current_env(), 
    h_called_in = caller_env(), 
    parent_of_current = parent.env(current_env())
  ))
}
list(e1_is = e1); exec(h, .env = e1); exec(h, .env = e1)

Output (note that h is evaluated in a different enviornment each time)

$e1_is
<environment: 0x000002e731f5c1b8>

$h_evaluated_in
<environment: 0x000002e731f59cb0>

$h_called_in
<environment: 0x000002e731f5c1b8>

$parent_of_current
<environment: R_GlobalEnv>

$h_evaluated_in
<environment: 0x000002e731f58180>

$h_called_in
<environment: 0x000002e731f5c1b8>

$parent_of_current
<environment: R_GlobalEnv>

Now we can go back to your examples.

  • In the first case, f is called in e1. f is executed in some isolated environment (call it temp). In temp, f calls env_get(...). The caller_env() of f is thus e1. e1's parent environment is global_env(). env_get(...) stops in e1 because it finds a binding a there.

  • In the second case, g is called in e1. g is executed in some isolated environment (call it temp). In temp, g calls f(). The caller_env() of f is thus temp. temp's parent environment is global_env(). env_get(...) cannot find binding a in either temp or global_env() and thus return "not found".

  • In the last case, g is called in e1. g is executed in some isolated environment (call it temp). In temp, g calls f(). The caller_env() of f is thus temp. The caller_env(2) of f is e1 since g calls f() and g is called in e1. e1's parent environment is global_env(). env_get(...) stops in e1 because it finds a binding a there.

Futher reading

Environments - Advanced R

ekoam
  • 8,744
  • 1
  • 9
  • 22