有人可以指出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中的一些权威指南,明确指出除了函数之外的括号不会创建一个新的范围?
答案 0 :(得分:4)
我不确定C#
,但对于R
,这些函数会被懒惰地评估。
j
中的print(j)
在调用函数之前不会被评估。因此,当您仅使用调用funclist[[2]]('foo')
时,会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=..)
功能访问此类变量。但您无法直接以嵌套函数内部访问父对象的方式访问它。
答案 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#
行为。