R:装饰器功能的环境图

R: environment diagram for decorator function

我想为以下代码绘制一个环境图,其中包含错误以了解 R 在评估函数时的确切工作方式。

# emphasize text
emph <- function(f, style = '**') {
  function(...) {
    if (length(style) == 1) {
      paste(style, f(...), style)
    } else {
      paste(style[1], f(...), style[2])
    }
  }
}

# function to be decorated
tmbg <- function() {
  'tmbg are okay'
}

# a decorator function with self-referencing name
tmbg <- emph(tmbg)

我在计算装饰函数的调用表达式时出错

tmbg()
> Error: evaluation nested too deeply: infinite recursion / options(expressions=)?

我可以理解这与R中函数参数的惰性评估有关。感觉在全局框架中评估tmbg()时,返回的匿名函数中使用的f的名称绑定再次在全局框架中 tmbg 再次 returns 匿名函数并调用 f,从而导致无限递归调用。但是这张图片对我来说不是很清楚,因为我不完全知道 R 中使用的评估模型是什么,尤其是这种“惰性评估”。

下面画出环境图的主要部分,并说明Python中使用的求值规则等价代码。 我希望 R 也能得到这样的环境图,或者至少能得到 R 中使用的环境模型的清晰度。

# This is the equivalent python code 
def emph(f, style = ['**']):
    def wrapper(*args):
        if len(style) == 1:
            return style[0] + f(*args) + style[0]
        else:
            return style[0] + f(*args) + style[1]
    return wrapper
    
def tmbg():
    return 'tmbg are okay'

tmbg = emph(tmbg)

tmbg()

在第 12 行 tmbg = emph(tmbg) 处计算赋值语句时,需要先计算调用表达式 emph(tmbg)。在计算调用表达式的运算符时,它的形参f绑定到全局框架中的名称tmbg,它绑定到我们在全局框架中定义的函数,如下图所示。

接下来,完成调用表达式emph(tmbg)的计算后,其返回函数wrapper绑定到全局框架中的名称tmbg。但是 f 和实际函数 tmbg 的绑定仍然保留在由 emph 创建的本地框架中(下图中的 f1)。

因此在全局框架中计算tmbg()时,不会混淆哪个是装饰器函数(全局tmbg)哪个是要装饰的函数(f 在本地框架中)。这是与R不同的部分。

看起来 R 所做的是它在全局框架中将绑定从 f -> function tmbg() 更改为 f -> 名称 tmbg,这又是绑定到 function wrapper(*args) 调用 f 本身,从而导致这种无限递归。但它也可能是一个完全不同的模型,R 并没有真正将 f 绑定到任何对象,而是一个名称 tmbg 并忽略该名称代表的内容。当它开始评估时,它会查找名称 tmbg 并找到由 tmbg <- emph(tmbg) 创建的全局名称并获得无限递归。但这听起来真的很奇怪,因为一旦我们将表达式作为该函数的参数传递,函数调用创建的局部作用域就不再(或部分地)用于“惰性求值”的目的。除了由管理命名空间和范围的函数调用创建的环境之外,还必须有一个并行的系统 运行。

无论哪种情况,我都不清楚环境模型和评估规则R。我想清楚这些,并为R代码画一个环境图,如果可能的话,如下图。

问题是不了解环境。问题是理解惰性求值。

由于惰性求值,f 只是一个承诺,直到匿名函数 运行 并且 tmbg 已被重新定义时才会被求值。要在 emph 为 运行 时强制计算 f,请添加标记的 ### force 语句以强制执行。没有更改其他行。

就环境而言,匿名函数从 emph 获取 f,在 emph 中,f 是一个承诺,除非我们添加 force 语句,否则在匿名函数 运行 之前不会在调用者中查找它。

emph <- function(f, style = '**') {
  force(f)  ###
  function(...) {
    if (length(style) == 1) {
      paste(style, f(...), style)
    } else {
      paste(style[1], f(...), style[2])
    }
  }
}

# function to be decorated
tmbg <- function() {
  'tmbg are okay'
}

# a decorator function with self-referencing name
tmbg <- emph(tmbg)

tmbg()
## [1] "** tmbg are okay **"

我们可以使用 pryr 包查看 promise。

library(pryr)

emph <- function(f, style = '**') {
  str(promise_info(f))
  force(f)
  cat("--\n")
  str(promise_info(f))
  function(...) {
    if (length(style) == 1) {
      paste(style, f(...), style)
    } else {
      paste(style[1], f(...), style[2])
    }
  }
}

# function to be decorated
tmbg <- function() {
  'tmbg are okay'
}

tmbg <- emph(tmbg)

这导致此输出显示 f 最初未计算,但在调用 force 后它包含 f 的值。如果我们不使用强制,匿名函数将在第一个 promise_info() 输出中显示的状态下访问 f,因此它所知道的只是一个符号 tmbg 以及在哪里寻找它(全局环境)。

List of 4
 $ code  : symbol tmbg
 $ env   :<environment: R_GlobalEnv> 
 $ evaled: logi FALSE
 $ value : NULL
--
List of 4
 $ code  : symbol tmbg
 $ env   : NULL
 $ evaled: logi TRUE
 $ value :function ()  
  ..- attr(*, "srcref")= 'srcref' int [1:8] 1 13 3 5 13 5 1 3
  .. ..- attr(*, "srcfile")=Classes 'srcfilecopy', 'srcfile' <environment: 0x00000000102c3730>