在这种情况下,R和C#有哪些范围规则?

时间:2013-04-26 14:39:58

标签: c# r scope

有人可以指出C#和R的哪个范围规则不同,以便它们在以下示例中产生不同的结果?

C#

public static void Main(string[] args)
{
    var funs = new Action<string>[5];
    for (int i = 0; i < 5; i++)
    {
        int j = i;
        {
            funs[j] = x => Console.WriteLine(j);
        }
    }
    funs[2]("foo"); // prints 2
}

[R

funclist <- list()
for(i in 1:5)
{
  {
    j<-i
    funclist[[j]] <- function(x) print(j)
  } // the 5 inner blocks share the same scope?
}

funclist[[2]]('foo') // prints 5

更新

这个问题很有用R: usage of local variables

我相信我发现了差异 - 在R中有 NO文件说一个块会创建一个新的范围。所以只有函数可以创建一个新的范围,因为循环不能,括号不能。

任何人都可以给我链接到R中的一些权威指南,明确指出除了函数之外的括号不会创建一个新的范围?

5 个答案:

答案 0 :(得分:4)

我不确定C#,但对于R,这些函数会被懒惰地评估。

j中的print(j)在调用函数之前不会被评估。因此,当您仅使用调用funclist[[2]]('foo')时,R搜索j的值,该值在最后一次停止时为5.但是,请尝试以下操作你会看到这个值根本与循环无关。

j <- "Some Other Value"
funclist[[2]]('foo')

评论中有关问题的更新

R中,for循环不会影响范围或环境。 (另一方面,functionsapply类似循环。因此,为了澄清,您无法初始化嵌套*环境*中的变量,并从环境[1]访问它。然而,你可以做相反的事情;这是从子环境中访问父环境的对象。

正如Ross Ihaka的标志中所描绘的那样 Ross Ihaka & Robert Gentleman 来源:http://www.nytimes.com/2009/01/07/technology/business-computing/07program.html?pagewanted=all

有关R中范围界定的更多信息,请参阅:  http://cran.r-project.org/doc/contrib/Fox-Companion/appendix-scope.pdf

[1]上述陈述并非完全正确。您可以使用get(.., envir=..)功能访问此类变量。但您无法直接以嵌套函数内部访问父对象的方式访问它。

答案 1 :(得分:3)

我对R一无所知,但我可以告诉你C#中发生了什么。

在C#中输入函数时,您将获得该函数的激活; “激活”跟踪该函数调用使用的所有变量和临时值的状态。这就是函数递归的方式;每个调用都有自己的激活,因此可以拥有自己的局部变量值。

在C#中,函数获得自己的激活的想法实际上扩展到块。当你说:

public static void Main(string[] args)
{
    var funs = new Action<string>[5];
    for (int i = 0; i < 5; i++)
    {
        int j = i;
        {
            funs[j] = x => Console.WriteLine(j);
        }
    }
    funs[2]("foo"); // prints 2
}

这在逻辑上就像你说的那样

public static void Main(string[] args)
{
    var funs = new Action<string>[5];
    for (int i = 0; i < 5; i++)
        D(i, funs);
    funs[2]("foo"); // prints 2
}

static void D(int i, Action<string>[] funs)
{
    int j = i;
    {
        funs[j] = x => Console.WriteLine(j);
    }
}

每当你完成循环,你就会得到一个全新的变量j ,lambda将其关闭。

我怀疑R中发生的事情是,每次通过循环而不是获得全新的j时,您更新之前的j ,而不是完全是C#所做的。也就是说,看起来你的R程序实际上等同于:

public static void Main(string[] args)
{
    var funs = new Action<string>[5];
    int j;
    for (int i = 0; i < 5; i++)
    {
        j = i;
        {
            funs[j] = x => Console.WriteLine(j);
        }
    }
    funs[2]("foo");
}

打印4,因为这是j的最后一个值。

有关详细信息,请参阅http://ericlippert.com/2009/11/12/closing-over-the-loop-variable-considered-harmful-part-one/

答案 2 :(得分:3)

对@ RicardoSaporta的答案进行扩展。

在R中运行命令:

environment(funclist[[2]])

并且您将看到函数的父环境是全局环境,因此j永远不会成为函数本地的,当它们运行时,它们将找不到任何名为{{1的变量在本地环境中,他们接下来要做的就是查看封闭环境,即全局环境,并使用j的当前值。如果您执行j然后运行任何功能,您将收到无法找到的错误rm(j)

如果您希望每个函数都有自己的封闭环境,其值为j,那么您可以执行以下操作:

j

答案 3 :(得分:2)

Section 3.5 of R Language Definition描述了R中变量的范围规则。

在第3.5.2节中说明了

  

对函数的每次调用都会创建一个框架,其中包含在函数中创建的局部变量,并在一个环境中进行评估,该环境组合创建一个新环境。

所以它(仅)调用一个创建一个新的词法范围的函数。在函数中,您可以通过调用其他函数来创建其他范围(事实上,请参阅local()函数的帮助以获取其唯一目的是创建新范围的函数)。

因此,与C ++不同,R中的范围仅在功能级别,而不是块级别。

答案 4 :(得分:1)

我认为C#执行方式是程序性的。在创建列表时评估所有函数参数。

R函数中,只有在第一次调用函数时才会计算参数。这意味着在调用funclist[[2]]('foo')时,for循环已经过评估,j的最后一个值为5

注意:可以使用force,这会导致C#行为。