你如何在R中使用“<< - ”(范围分配)?

时间:2010-04-13 10:04:46

标签: r scoping lexical-scope r-faq

我刚读完了scoping in the R intro,对<<-作业非常好奇。

手册显示<<-的一个(非常有趣)的例子,我觉得我理解。我仍然缺少的是这可能有用的背景。

因此,我希望您阅读的内容是关于<<-的使用何时有用/有用的示例(或示例链接)。使用它的危险可能是什么(它看起来容易松散),以及您可能想要分享的任何提示。

6 个答案:

答案 0 :(得分:165)

<<-与闭包相结合以保持状态最有用。这是我最近的一篇论文的一部分:

闭包是由另一个函数写的函数。闭包是因为它们包含父函数的环境,并且可以访问该函数中的所有变量和参数。这很有用,因为它允许我们有两个级别的参数。一级参数(父级)控制函数的工作方式。另一个级别(孩子)完成工作。以下示例显示了如何使用此构思生成一系列电源功能。父函数(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

答案 1 :(得分:33)

<<-视为等同于assign(如果您将该函数中的inherits参数设置为TRUE)会有所帮助。 assign的好处是它允许您指定更多参数(例如环境),因此在大多数情况下,我更倾向于使用assign而不是<<-

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

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

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

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

答案 2 :(得分:6)

我使用<<-的一个地方是使用tcl / tk的简单GUI。一些最初的例子有它 - 因为你需要区分localfullness的局部变量和全局变量。参见例如

 library(tcltk)
 demo(tkdensity)

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

答案 3 :(得分:4)

f <- function(n, x0) {x <- x0; replicate(n, (function(){x <<- x+rnorm(1)})())}
plot(f(1000,0),typ="l")

答案 4 :(得分:2)

On this subject I'd like to point out that the <<- operator will behave strangely when applied (incorrectly) within a for loop (there may be other cases too). Given the following code:

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

you might expect that the function would return the expected sum, 6, but instead it returns 0, with a global variable mySum being created and assigned the value 3. I can't fully explain what is going on here but certainly the body of a for loop is not a new scope 'level'. Instead, it seems that R looks outside of the fortest function, can't find a mySum variable to assign to, so creates one and assigns the value 1, the first time through the loop. On subsequent iterations, the RHS in the assignment must be referring to the (unchanged) inner mySum variable whereas the LHS refers to the global variable. Therefore each iteration overwrites the value of the global variable to that iteration's value of i, hence it has the value 3 on exit from the function.

Hope this helps someone - this stumped me for a couple of hours today! (BTW, just replace <<- with <- and the function works as expected).

答案 5 :(得分:2)

The <<- operator can also be useful for Reference Classes when writing Reference Methods. For example:

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