1

有人可以指出 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 中,没有文件说一个块会创建一个新的范围。所以只有函数可以创建一个新的范围,for循环不能,括号不能。

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

4

5 回答 5

4

我不确定C#,但至于R,函数是懒惰地评估的。

jprint(j)调用函数之前,不会对 in 进行评估。因此,当您调用funclist[[2]]('foo')only then时,会R搜索 的值j,上次停止时为 5。但是,尝试以下操作,您会发现该值与循环完全无关。

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

关于评论中问题的更新

R中,for 循环不会影响范围或环境。(另一方面,functions就像apply循环一样)。因此,为了澄清,您不能在嵌套 * 环境 * 中初始化变量并从环境 [1] 访问它。但是,您可以做相反的事情;即从子环境中访问父环境的对象。

罗斯伊哈卡和罗伯特绅士 正如罗斯·伊哈卡 (Ross Ihaka) 的标志源中的这张图片所清楚描绘的那样 : 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=..)函数访问这样的变量。但是您不能以从嵌套函数内部访问父对象的方式直接访问它。

于 2013-04-26T14:48:33.393 回答
3

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

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

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

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 会关闭该变量。

j我怀疑 R 中发生的事情是,不是每次通过循环都获得一个全新的,而是更新上一个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/closure-over-the-loop-variable-considered-harmful-part-one/ 。

于 2013-04-26T15:27:10.033 回答
3

扩展一下@RicardoSaporta 的回答。

在 R 中运行命令:

environment(funclist[[2]])

并且您会看到函数的父环境是全局环境,因此由于j从未对函数进行本地化,因此当它们运行时,它们将找不到j在本地环境中命名的任何变量,因此接下来它们将要做的是查看封闭环境,这是全局环境,并使用那里的任何当前值j。如果您这样做,rm(j)然后运行任何功能,您将收到一个无法找到的错误j

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

funclist <- list()
for(i in 1:5)
{
   funclist[[i]] <- local( {
    j <- i
    function(x) print(j)
    } )
}

funclist[[2]]('foo') # prints 2
于 2013-04-26T15:29:47.993 回答
2

R 语言定义的第 3.5 节描述了 R 中变量的作用域规则。

在第 3.5.2 节中,它指出

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

因此,(仅)调用创建新词法范围的函数。在一个函数中,您可以通过调用其他函数来创建额外的作用域(事实上,请参阅local()函数的帮助以了解其唯一目的是创建新作用域的函数)。

因此,与 C++ 不同,R 中的作用域仅在函数级别,而不是块级别。

于 2013-04-26T19:57:01.163 回答
1

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

仅在R第一次调用函数时才评估函数中的参数。这意味着当funclist[[2]]('foo')被调用时,for循环已经被评估并且j具有最后一个值5

注意:可以使用force这将导致C#类似的行为。

于 2013-04-26T15:23:03.603 回答