env_parent():使用默认参数或显式传递默认参数时的不同结果

env_parent(): different results when using default argument or when passing explicitly default argument

我注意到使用 rlang 包中的 env_parent() 和使用 env_parent(caller_env()) 时的不同行为,尽管 caller_env() 首先是 env_parent() 的默认参数参数:

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>

我对环境不是很熟悉,我只能基于 shiny app 来准备 MRE(我使用 shiny app 注意到了这一点)。将应用程序放在两个文件中很重要 - ui.Rserver.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())
  })
  
}

我在 运行 应用程序后看到此输出:

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

这是为什么?

对您的问题的简短回答是,显式调用带参数的函数与调用函数并让它自己提供与默认值相同的参数相比是有区别的。在后一种情况下,调用者环境是函数的执行环境。在前一种情况下,调用者环境是函数被显式调用的地方。

我花了一段时间才找到对正在发生的事情的一个很好的可视化,但我想我找到了一个。

让我们创建两个类似于 {rlang} 的 env_parentcaller_env 的函数。不同的是,myCallEnv不仅会return调用函数的执行环境,还会显示lobstr::cst():

的调用轨迹树
myCallEnv <- function() {
    print(lobstr::cst())
    caller_env(n = 1)
  }

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

现在让我们用默认参数调用 myEnvParent 一次,然后显式提供相同的默认参数调用一次:

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"

比较两个输出我们可以看到,在第一次调用中,myEnvParent()myCallEnv()是从myEnvParent()的执行环境中调用的。父环境是全局环境R_GlobalEnv.

在第二次调用中,myEnvParent(myCallEnv())myCallEnv() 是从全局环境调用的,因此其父级是搜索路径上的第二个环境:

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"