愚弄 R 的方法调度

Fooling R's Method Dispatch

提问人:cdalitz 提问时间:11/14/2023 更新时间:11/16/2023 访问量:186

问:

我正在寻找一个示例,其中函数名称中的点会导致 R 的方法调度选择错误的函数。一个自然的候选函数似乎是内置函数,当应用于类“test”的对象时,它应该被混淆。t.test()t()

然而,奇怪的是,以下代码实际上调用了转置函数,而不是:t.test()

> x <- structure(cbind(1:4,2:5), class="test")
> class(x)
[1] "test"
> t(x)
     [,1] [,2] [,3] [,4]
[1,]    1    2    3    4
[2,]    2    3    4    5

这引发了两个问题:

  1. 为什么对象被转置而不是调用,因为它应该是由于方法调度的规则?t.test()
  2. 通过定义一个内部带有点的函数来搞砸方法调度过程的(简单)示例是什么?
R 重载

评论

2赞 Konrad Rudolph 11/14/2023
有趣的是,与实际的 S3 调度不一致,实际上会返回这里。我相信这是一个错误(至少它应该给出一致的结果!),但是当在 r-devel 邮件列表上询问这个问题时,我基本上没有得到任何回应,但我得到的印象是,即使是(一些)核心 R 维护者也不完全理解 S3 调度。getS3method()t.test
1赞 Konrad Rudolph 11/14/2023
另外,在调用之前尝试:它将导致被调用。这种行为在 R 3.5 中发生了变化。registerS3method('t', 'test', t.test)t(x)t.test()
0赞 Carl Witthoft 11/14/2023
我的“投票”,就像它一样,是迫使库重命名为没有“点”的东西(我知道,火焰战争),或者更好的是,将名称更改为独特而明显的东西,例如.由于实际上不是属于 的方法,因此应用于重载类名的类名永远不应该调用,因为它不是任何方法子方法。这是有意为之还是解释器/调度器中的错误?statst.teststudentTt.testtt()t.test
0赞 cdalitz 11/15/2023
@carl-witthoft 在 R 中有不同的方法可以定义类,对于 S4 类和引用类,你是对的。然而,对于 S3 类来说,是一种类“测试”的方法,前提是存在这样的类并且存在泛型函数。这只是 S3 处理类的方式所固有的歧义。实际上,我的问题是,如何滥用这种歧义来达到教学目的:我想解释为什么,例如,谷歌编码风格指南不鼓励在函数名称中使用点。t.test()t()
0赞 user2554330 11/15/2023
你说“方法调度的规则”,但没有说你在哪里读到它们。R 附带的手册中存在错误;这些都值得向开发人员报告。其他人编写的文档中也存在错误。有时这些值得向这些作者报告,但通常不值得麻烦。

答:

3赞 MrFlick 11/14/2023 #1

您还必须考虑评估函数的环境。例如,如果你在环境中运行它,你会得到stats

t(x)
#      [,1] [,2] [,3] [,4]
# [1,]    1    2    3    4
# [2,]    2    3    4    5
# attr(,"class")
# [1] "test"

evalq(t(x), envir=asNamespace("stats"))
#   One Sample t-test
# 
# data:  x
# t = 6.4807, df = 7, p-value = 0.0003402
# alternative hypothesis: true mean is not equal to 0
# 95 percent confidence interval:
#  1.905392 4.094608
# sample estimates:
# mean of x 
#         3 

所以问题是该函数是在命名空间中定义的,而 在 .默认情况下,首先找到转置。t()baset.test()stats

评论

0赞 cdalitz 11/15/2023
就像之前的搜索路径一样,我认为它应该有优先权。在 R 版本 3.5.1 中,它实际上具有,并且确实调用了 !它一定与在某个更高版本的 R 版本中引入的包中隐藏 S3 方法有关,因为在 R 4.2.1 中,这不再有效。请看@user2554330的答案。statsbaset()t.test()
3赞 user2554330 11/15/2023 #2

S3 调度很复杂。如果定义一个在全局环境中命名的函数,并使用 class 对对象调用该函数,则将调用该函数。但是,如果位于包中,则需要将其注册为 S3 方法才能以这种方式进行处理。(注册发生在文件中或通过执行 。t.testt()"test"t.test()t.testNAMESPACEregisterS3method()

因此,这里有一个你正在寻找的示例:

t.test <- t.test
x <- 1
class(x) <- "test"
t(x)
#> Error in t.test.default(x): not enough 'x' observations

创建于 2023-11-14 with reprex v2.0.2

2018 年 R 3.5.0 发布时,有一些关于此的消息:第 8 个“新功能”在

https://developer.r-project.org/blosxom.cgi/R-3-5-branch/NEWS/2018/03/29#n2018-03-29

评论

0赞 cdalitz 11/15/2023
谢谢,这似乎是问题所在。刚刚在 R 3.51 中测试了它,那里不需要第一行:即使没有它,也被调用。因此,我猜想在包中隐藏 S3 函数是在后来的 R 版本中引入的。欢迎在发生这种情况时提供提示。t.test()t()