【问题标题】:Environment chaining in RR中的环境链接
【发布时间】:2012-06-20 01:53:07
【问题描述】:

在我的 R 开发中,我需要将函数原语包装在 proto 对象中,以便在调用对象的 $perform() 方法时可以自动将许多参数传递给函数。函数调用在内部通过do.call() 进行。一切都很好,除非函数试图从定义它的闭包中访问变量。在这种情况下,该函数无法解析名称。

这是我发现的最小的重现该行为的示例:

library(proto)

make_command <- function(operation) {
  proto(
    func = operation,
    perform = function(., ...) {
      func <- with(., func) # unbinds proto method
      do.call(func, list(), envir=environment(operation))
    }  
    )
}

test_case <- function() {
  result <- 100  
  make_command(function() result)$perform()
}

# Will generate error:
# Error in function ()  : object 'result' not found
test_case()

我有一个可重现的testthat 测试,它还输出大量诊断输出。诊断输出让我难过。通过查找父环境链,位于函数内部的我的诊断代码找到并打印了函数未能找到的相同变量。 See this gist.

如何正确设置do.call的环境?

【问题讨论】:

    标签: r tdd do.call proto


    【解决方案1】:

    这是与发帖者线下讨论后的最终答案:

    make_command <- function(operation) {
     proto(perform = function(.) operation())
    }
    

    【讨论】:

    • 感谢您对这个问题的多次跟进。你能帮我理解为什么会这样吗?是不是这样:(1) 调用proto() 中的. 指的是调用make_command() 的评估框架/环境,因此它成为父级(即@ 987654325@) 的新 proto 对象;这样(2)当在proto对象中没有找到operation时,在它的.super中查找并找到;然后 (3) operation 本身将调用 test_case() 的评估框架/环境作为环境(result 也存在于此)?
    • 一个原型对象是一个环境。这里的 proto 对象有一个组件,perform,它是一个函数/方法。 perform 的环境在插入时设置为 proto 对象,因此当 perform 运行时,它首先在 proto 对象中查找 operation,当在 proto 对象中找不到时,它会在 proto 对象的父对象中查找。未指定 proto 对象的父对象,因此它默认为定义 proto 对象的环境 - 这是 make_command 执行框架内的环境,它在那里找到 operation 作为参数。在 proto 主页上查看新的常见问题解答。
    • 感谢其他问题/评论和新的常见问题解答。
    【解决方案2】:

    我认为,如果您:

    • make_command() 中的匿名函数替换为命名函数。

    • 使该函数打开一个browser()(而不是尝试获取result)。这样你就可以环顾四周,看看你在哪里以及发生了什么。

    试试这个,它应该可以弄清问题的原因:

    test_case <- function() {
      result <- 100  
      myFun <- function() browser()
      make_command(myFun)$perform()
    }
    test_case()
    ## Then from within the browser:
    #
    parent.env(environment())
    # <environment: 0x0d8de854>
    # attr(,"class")
    # [1] "proto"       "environment"
    get("result", parent.env(environment()))
    # Error in get("result", parent.env(environment())) : 
    #   object 'result' not found
    #
    parent.frame()
    # <environment: 0x0d8ddfc0>
    get("result", parent.frame())  ## (This works, so points towards a solution.)
    # [1] 100
    

    这就是问题所在。尽管您认为您正在评估myFun(),其环境是test_case() 的评估框架,但您对do.call(func, ...) 的调用实际上是在评估func(),其环境是定义它的proto 环境。在自己的框架中查找并没有找到result 之后,对func() 的调用遵循词法范围的规则,然后在proto 环境中查找。它和 父环境都不包含名为 result 的对象,从而导致您收到错误消息。

    如果这不能立即生效,您可以继续在浏览器中四处寻找。以下是一些可能对您有所帮助的进一步电话:

    environment(get("myFun", parent.frame()))
    ls(environment(get("myFun", parent.frame())))
    environment(get("func", parent.env(environment())))
    ls(environment(get("func", parent.env(environment()))))
    

    【讨论】:

    • 谢谢,这些是有用的调试技巧。你会推荐什么阅读材料来真正理解 R 中的作用域链?
    • 关于您的答案的一点澄清: func 的环境是 proto 对象的,但不是因为这是定义它的地方。 Proto 将函数包装在一个类中以将它们转换为实例化方法。 with(., func) 解开它们并且 应该 已经工作了,除了 proto 的实现实际上重置了函数的环境,以便它的代码可以访问 proto 对象的内部。正是幕后发生的重置是行为(和混乱)的原因。
    • @Sim -- 感谢您的澄清。 John Chambers 的“数据分析软件”的第 13.3 节清楚地解释了 R 中的作用域。他所做的一件令人困惑的事情是始终将封闭环境称为“父环境”。我认为这是不幸的,因为它混淆了调用堆栈(父框架链)和封闭环境链之间的重要区别,评估器搜索以解析本地环境中不存在的符号值。
    • @Sim -- 在单独的说明中,将 make_command(function() result)$perform() 替换为 make_command(function() get("result", parent.frame()))$perform()(或其等效项)是否可以为您解决眼前的问题?
    • 只是对上述内容的澄清。当使用p &lt;- proto(... f = function(...) ...) 将函数/方法f 插入到原型对象p 中时,插入p 中的f 副本的环境更改为指向p,但其类仍然是@ 987654343@ 和所有其他方面f 保持不变。通过编写 p$f 实例化 f 会返回类 c("instantiatedProtoMethod", "function") 的对象。因此with(p, f) 并没有真正解开f,而是简单地返回f,就像它存在于p 中一样。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-23
    • 1970-01-01
    • 2016-09-22
    • 2018-06-09
    • 2014-08-08
    • 1970-01-01
    相关资源
    最近更新 更多