163

我刚刚读完R intro 中关于范围界定的<<-内容,并且对这项任务非常好奇。

该手册显示了一个(非常有趣的)示例<<-,我觉得我理解了。我仍然缺少的是何时有用的上下文。

所以我很想从你那里读到一些例子(或例子的链接),关于什么时候使用<<-可以是有趣/有用的。使用它可能有什么危险(看起来很容易忘记),以及您可能想要分享的任何提示。

4

6 回答 6

219

<<-与闭包一起维护状态最有用。这是我最近的一篇论文中的一段:

闭包是由另一个函数编写的函数。闭包之所以被称为闭包,是因为它们封装了父函数的环境,并且可以访问该函数中的所有变量和参数。这很有用,因为它允许我们有两个级别的参数。一级参数(父级)控制函数的工作方式。另一个级别(孩子)做这项工作。下面的例子展示了如何使用这个想法来生成一系列幂函数。父函数 ( power) 创建子函数 (squarecube),它们实际上完成了艰苦的工作。

power <- function(exponent) {
  function(x) x ^ exponent
}

square <- power(2)
square(2) # -> [1] 4
square(4) # -> [1] 16

cube <- power(3)
cube(2) # -> [1] 8
cube(4) # -> [1] 64

在两个级别上管理变量的能力还可以通过允许函数修改其父环境中的变量来跨函数调用维护状态。在不同级别管理变量的关键是双箭头赋值运算符 <<-。与通常<-在当前级别上始终有效的单箭头赋值 ( ) 不同,双箭头运算符可以修改父级别中的变量。

这使得可以维护一个计数器来记录函数被调用的次数,如下面的示例所示。每次new_counter运行,它都会创建一个环境,在这个环境中初始化计数器i,然后创建一个新的函数。

new_counter <- function() {
  i <- 0
  function() {
    # do something useful, then ...
    i <<- i + 1
    i
  }
}

新函数是一个闭包,它的环境是封闭环境。当闭包counter_onecounter_two运行时,每个都修改其封闭环境中的计数器,然后返回当前计数。

counter_one <- new_counter()
counter_two <- new_counter()

counter_one() # -> [1] 1
counter_one() # -> [1] 2
counter_two() # -> [1] 1
于 2010-04-13T14:18:18.157 回答
43

将其<<-视为等效于assign(如果您inherits将该函数中的参数设置为TRUE)会有所帮助。的好处assign是它允许你指定更多的参数(例如环境),所以我更喜欢在大多数情况下 使用assignover 。<<-

使用<<-andassign(x, value, inherits=TRUE)意味着“搜索所提供环境的封闭环境,直到遇到变量‘x’。” 换句话说,它将继续按顺序遍历环境,直到找到具有该名称的变量,并将其分配给该变量。这可以在函数的范围内,也可以在全局环境中。

为了了解这些函数的作用,您还需要了解 R 环境(例如使用search)。

当我运行大型模拟并且想要保存中间结果时,我经常使用这些函数。apply这允许您在给定函数或循环范围之外创建对象。这很有帮助,特别是如果您担心大型循环意外结束(例如数据库断开连接),在这种情况下您可能会丢失过程中的所有内容。这相当于在长时间运行的过程中将结果写入数据库或文件,只是它将结果存储在 R 环境中。

我对此的主要警告:要小心,因为您现在正在使用全局变量,尤其是在使用<<-. 这意味着您最终可能会遇到函数使用环境中的对象值的情况,而您希望它使用作为参数提供的对象值。这是函数式编程试图避免的主要事情之一(参见副作用)。我通过将我的值分配给一个唯一的变量名(使用带有一组或唯一参数的粘贴)来避免这个问题,这些变量名从未在函数中使用,但仅用于缓存,以防我稍后需要恢复(或做一些元-对中间结果的分析)。

于 2010-04-13T12:34:26.947 回答
9

我使用的一个地方<<-是使用 tcl/tk 的简单 GUI。一些初始示例有它 - 因为您需要区分局部变量和全局变量以实现状态完整性。参见例如

 library(tcltk)
 demo(tkdensity)

它使用<<-. 否则我同意 Marek :) -- 谷歌搜索可以提供帮助。

于 2010-04-13T12:12:30.843 回答
6

关于这个问题,我想指出,<<-当在 for 循环中(不正确地)应用运算符时(可能还有其他情况),运算符的行为会很奇怪。给定以下代码:

fortest <- function() {
    mySum <- 0
    for (i in c(1, 2, 3)) {
        mySum <<- mySum + i
    }
    mySum
}

您可能期望该函数将返回预期的总和 6,但它返回 0,并mySum创建了一个全局变量并将其赋值为 3。我无法完全解释这里发生了什么,但肯定是 a for循环不是新的范围“级别”。相反,似乎 R 看起来在fortest函数之外,找不到mySum要分配的变量,所以创建一个变量并赋值 1,这是第一次通过循环。在随后的迭代中,赋值中的 RHS 必须引用(未更改的)内部mySum变量,而 LHS 引用全局变量。因此,每次迭代都会将全局变量的值覆盖为该迭代的值i,因此它在退出函数时具有值 3。

希望这对某人有所帮助-今天这让我难倒了几个小时!(顺便说一句,只需替换<<-<-并且该功能按预期工作)。

于 2015-06-15T14:58:15.187 回答
5
f <- function(n, x0) {x <- x0; replicate(n, (function(){x <<- x+rnorm(1)})())}
plot(f(1000,0),typ="l")
于 2010-04-13T12:23:22.210 回答
3

在编写参考方法时,该<<-运算符对于参考类也很有用。例如:

myRFclass <- setRefClass(Class = "RF",
                         fields = list(A = "numeric",
                                       B = "numeric",
                                       C = function() A + B))
myRFclass$methods(show = function() cat("A =", A, "B =", B, "C =",C))
myRFclass$methods(changeA = function() A <<- A*B) # note the <<-
obj1 <- myRFclass(A = 2, B = 3)
obj1
# A = 2 B = 3 C = 5
obj1$changeA()
obj1
# A = 6 B = 3 C = 9
于 2015-06-15T15:12:24.320 回答