在包/命名空间环境中,同名函数的值/引用相等性?

11

让我们获取环境中的 "namespace:stats" 和 "package:stats"

ns = getNamespace( "stats" )
pkg = as.environment( "package:stats" )

现在让我们在两个中获取函数“sd”:

nsSd = get( "sd" , envir = ns , inherits = FALSE )
pkgSd = get( "sd" , envir = pkg , inherits = FALSE )

它们是相同的吗? 是! 但“相同”意味着什么? 引用相等还是值相等?

identical( nsSd , pkgSd )
这意味着使用引用相等性进行比较,因为以下代码的返回值为FALSE:
test1 = function() {}
test2 = function() {}
identical( test1 , test2 )

但如果这是真的,这意味着环境框架中可以包含函数指针和函数对象。更加复杂的问题在于,一个函数可以“存在”于一个环境中,但该函数可以被告知它执行的环境是另一个环境。 Chambers SoDA似乎没有答案(这是一本密集的书,也许我错过了!)

因此,我想要一个明确的答案。以下哪些是正确的?或者这里是否存在错误三分法?

  1. nsSdpkgSd是两个不同的对象(虽然它们互为副本),其中pkgSd中的对象具有其执行环境为ns
  2. nsSdpkgSd是指向同一对象的指针。
  3. nsSd是指向pkgSd的指针,因此它们被视为相同。

值得一看的是内部“identical”函数的C代码。http://svn.r-project.org/R/trunk/src/main/identical.c - Richie Cotton
我认为你把事情搞得有点复杂了。我们知道只有一个sd()函数,所以你看到的任何差异都是由于你通过环境和命名空间的访问路径不同造成的。 - Dirk Eddelbuettel
根据 R 内部手册,这些函数的类型为 CLOSXP。匹配指针被视为相同,否则它会检查相同的 formalsbodyCLOENV(x) - Richie Cotton
您的第二个示例在检查函数体时失败。identical(body(test1), body(test2))的结果为FALSE - Richie Cotton
Dirk - 能否详细说明一下?我不知道只有一个sd(),这就是我发帖的原因 =)看起来你在区分环境和名称空间,你是指包环境和名称空间环境吗?我想你是在说答案可能是上述2或3中的一个? - Suraj
Richie - 感谢您检查 C 代码!测试1的identical(body(test1)..)失败很奇怪。似乎不一致?那么这里的答案可能是第二个选项吗? - Suraj
2个回答

5
它们是指向同一对象的指针。使用这个回答另一个问题,我们可以检查两个对象是否引用内存中的同一位置。
are_same <- function(x, y)
{
  f <- function(x) capture.output(.Internal(inspect(x)))
  all(f(x) == f(y))
}

are_same(nsSd, pkgSd) #TRUE
are_same(1:5, 1:5)    #FALSE

我开始了一个悬赏,将在24小时等待期结束后授予您,以表达我的感谢。 - Suraj
非常慷慨的你。很高兴我能有所帮助。 - Richie Cotton
3
非常酷的功能。直到现在,我不知道当我执行 j <- rnorm(1e7); k <- j时,jk 只是指向相同内存位置的指针。但是当我执行 k[1] <- 1 时,整个向量 k 需要被复制到一个新位置,因为它已经不同了。所以 k <- jk[1] <- 1 快得多。R核心开发人员为我们提供了一个很好的服务,而我直到现在才真正意识到它的价值! - Josh O'Brien
2
@JoshO'Brien:是的,尽管R通常表现得按值传递,但为了实现更好的性能,它会在可能的情况下秘密地传递对象的引用。 - Richie Cotton
这是通过承诺实现的吗,就像函数参数的惰性求值一样实现的吗? - Suraj
@SFun28:我认为这取决于对象的类型。我记得环境总是作为引用传递的;其他的东西……好吧,你就必须阅读R-internals才能找出答案了。R被巧妙地设计成你不需要担心它(除非你有加入R-core的愿望)。http://cran.r-project.org/doc/manuals/R-ints.html - Richie Cotton

4

这并不是针对您主要问题的答案。但在这个问题上,我同意Dirk的观点:只有一个sd()函数,具体取决于情况,可以通过不同的作用域路径进行访问。例如,当您在命令行中键入sd(x)时,将通过其在package:stats环境的框架中的条目找到与名称sd对应的函数。当您键入stats:::sd(x)时,或者当stats包中的另一个函数调用sd(x)时,将通过在namespace:stats环境中进行搜索来找到它。


相反,我只想指出您使用test1()test2()的示例并没有真正暗示任何关于评估为identical的对象的"参考相等性"的内容。要查看这两个对象不是identical的真正原因,请查看使用str()揭示的它们的结构:

test1 <- function() {}
test2 <- function() {}
identical( test1 , test2 )
# [1] FALSE

str(test1)
# function ()  
#  - attr(*, "srcref")=Class 'srcref'  atomic [1:8] 1 13 1 25 13 25 1 1
#   .. ..- attr(*, "srcfile")=Classes 'srcfilecopy', 'srcfile' <environment: 0x01613f54> 

str(test2)
# function ()  
#  - attr(*, "srcref")=Class 'srcref'  atomic [1:8] 1 13 1 25 13 25 1 1
#   .. ..- attr(*, "srcfile")=Classes 'srcfilecopy', 'srcfile' <environment: 0x01615730> 

如果你向右滚动代码框,你会发现这两个函数在它们的属性之一上有所不同,即与其源文件相关联的环境。 (我对那个属性不是很了解,但这并不重要。重点是它们不是“相同的”!)如果你告诉R,你不想保留每个创建的函数的源文件属性数据,“identical(test1,test2)”的“意外”行为就会消失:
options(keep.source=FALSE)
test1 <- function() {}
test2 <- function() {}
identical( test1 , test2 )
# [1] TRUE

谢谢,乔希!这是一篇富有洞见的文章。我感觉 Chambers 在呼唤 re:sourcefile 属性 =) - Suraj
@SFun28 - 很高兴在这里看到你回来了。关于 Chambers 的 SoDA,"dense" 是对这本书的很好描述;而让我觉得惊奇的是,里面没有浪费一页纸。它确实会回报你阅读所付出的任何努力,这一点我很欣赏。干杯。 - Josh O'Brien
同意...这篇文章充满了好东西。这篇文章是我在撰写关于R如何搜索和查找内容的博客文章时所缺少的"遗漏环节"。它是我从过去的帖子、Chambers和其他来源中发现的结果,也是尝试比search()更深入地探索。我认为没有任何一种来源能够清楚地解释这个过程,并提供易于跟随的视觉效果。在发布之前,我很乐意给您发送一份预先副本以供批评。 - Suraj
谢谢,乔希!我已经复制下了你的电子邮件。抱歉回复晚了,我得为情人节准备一些东西。如果可以的话,希望你还能删除那条评论,如果不能的话也很抱歉。大概在接下来的两三周内我会给你发送一个草稿。 - Suraj
@SFun28 很好。评论已删除,我期待着在你有机会工作时看到草稿。 - Josh O'Brien

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接