3

I have noticed different behavior when using env_parent() from rlang package and when using env_parent(caller_env()), although caller_env() is a default argument for env_parent() first parameter:

library(rlang)
env_parent
#> function (env = caller_env(), n = 1) 
#> {
#>     env_ <- get_env_retired(env, "env_parent()")
#>     while (n > 0) {
#>         if (is_empty_env(env_)) {
#>             abort("The empty environment has no parent")
#>         }
#>         n <- n - 1
#>         env_ <- parent.env(env_)
#>     }
#>     env_
#> }
#> <bytecode: 0x000000001d8ad000>
#> <environment: namespace:rlang>

I'm not very familiar with environments and I was able to prepare MRE only based on shiny app (I have noticed this using shiny app). It is important to have app in two files - ui.R and server.R:

ui.R

library(shiny)
library(rlang)

parent <<- function() {
  env_parent()
}

parents_default_arg_passed <<- function() {
  env_parent(caller_env())
}

ui <- fluidPage(
  textOutput("env_parent"),
  textOutput("env_parents_default_arg_passed")
)

server.R

server <- function(input, output, session) {
  
  output$env_parent <- renderPrint({
    names(parent())
  })
  
  output$env_parents_default_arg_passed <- renderPrint({
    names(parents_default_arg_passed())
  })
  
}

I see this output after running the app:

[1] "ui"
[1] "~" ".__tidyeval_quosure_mask__."

Why is that?

gss
  • 1,334
  • 6
  • 11

1 Answers1

1

The short answer to your question is, there is a difference between explicitly calling a function with an argument, compared to calling the function and letting it supply the same argument as default by itself. In the latter case, the caller environment is the execution environment of the function. In the former case the caller environment is where the function was explicitly called from.

It took me a while to find a nice visualization of what is going on, but I think I found one.

Let's create two functions similar to {rlang}'s env_parent and caller_env. The difference is that myCallEnv will not only return the execution environment of the calling function, it will also show the call strack tree with lobstr::cst():

myCallEnv <- function() {
    print(lobstr::cst())
    caller_env(n = 1)
  }

myEnvParent <- function(e = myCallEnv()) {
  env_parent(env = e)
}

Now let's call myEnvParent one time with its default argument and one time by supplying the same default argument explicitly:

myEnvParent()
#    ▆
# 1. └─global myEnvParent()
# 2.   ├─rlang::env_parent(env = e)
# 3.   │ └─rlang:::get_env_retired(env, "env_parent()")
# 4.   │   └─rlang::is_environment(x)
# 5.   └─global myCallEnv()
# 6.     ├─base::print(lobstr::cst())
# 7.     └─lobstr::cst()
# NULL
# <environment: R_GlobalEnv>

myEnvParent(myCallEnv())
#    ▆
# 1. ├─global myEnvParent(myCallEnv())
# 2. │ └─rlang::env_parent(env = e)
# 3. │   └─rlang:::get_env_retired(env, "env_parent()")
# 4. │     └─rlang::is_environment(x)
# 5. └─global myCallEnv()
# 6.   ├─base::print(lobstr::cst())
# 7.   └─lobstr::cst()
# NULL
# <environment: package:rlang>
# attr(,"name")
# [1] "package:rlang"
# attr(,"path")
# [1] # "/Library/Frameworks/R.framework/Versions/4.0/Resources/library/rlang"

Comparing both outputs we can see that in the first call, myEnvParent(), myCallEnv() is called from the execution environment of myEnvParent(). The parent environment is the global environment R_GlobalEnv.

In the second call, myEnvParent(myCallEnv()), myCallEnv() is called from the global environment and therefore its parent is the second environment on the search path:

searchpaths()
# [1] ".GlobalEnv"                                                              
# [2] "/Library/Frameworks/R.framework/Versions/4.0/Resources/library/rlang"    
# [3] "tools:rstudio"                                                           
# [4] "/Library/Frameworks/R.framework/Versions/4.0/Resources/library/stats"    
# [5] "/Library/Frameworks/R.framework/Versions/4.0/Resources/library/graphics" 
# [6] "/Library/Frameworks/R.framework/Versions/4.0/Resources/library/grDevices"
# [7] "/Library/Frameworks/R.framework/Versions/4.0/Resources/library/utils"    
# [8] "/Library/Frameworks/R.framework/Versions/4.0/Resources/library/datasets" 
# [9] "/Library/Frameworks/R.framework/Versions/4.0/Resources/library/methods"  
#[10] "Autoloads"                                                               
#[11] "/Library/Frameworks/R.framework/Resources/library/base"   
TimTeaFan
  • 17,549
  • 4
  • 18
  • 39
  • 1
    Thank you very much for explanation. I need some time to fully understand this, but I really appreciate your example. – gss Jan 15 '22 at 21:14