为什么 names(x)<-y 和 "names<-"(x,y) 不等价?

问题描述

考虑以下事项:

y<-c("A","B","C")  
x<-z<-c(1,2,3)  
names(x)<-y
"names<-"(z,y)

如果您运行此代码,您会发现 names(x)<-y"names<-"(z,y) 不同。特别是,可以看到 names(x)<-y 实际上更改了 x名称,而 "names<-"(z,y) 返回 z 并更改了名称

这是为什么?我的印象是,正常编写函数和将其编写为中缀运算符之间的区别只是语法之一,而不是实际更改输出的东西。文档中哪里讨论了这种差异?

解决方法

简短回答:names(x)<-y 实际上是 x<-"names<-"(x,y) 的糖,而不仅仅是 "names<-"(x,y)。请参阅 the R-lang manual,第 18-19 页(PDF 的第 23-24 页),这与示例基本相同。

例如,names(x) <- c("a","b") 相当于:

`*tmp*`<-x
x <- "names<-"(`*tmp*`,value=c("a","b"))
rm(`*tmp*`)

如果对getter/setter比较熟悉,可以认为如果somefunction是getter函数,那么somefunction<-就是对应的setter。在 R 中,每个对象都是不可变的,将 setter 称为 replacement 函数更正确,因为该函数实际上创建了一个与旧对象相同的新对象,但添加/修改/删除了一个属性并用这个新对象替换旧对象。

例如在案例示例中,names 属性不仅添加到 x;而是创建了一个具有 x 相同值但带有 names 的新对象并将其链接到 x 符号。

由于对于为什么在语言文档中而不是直接在 ?names 上讨论该问题仍然存在一些疑问,这里简要回顾一下 R 语言的这一属性。

  • 您可以使用您想要的名称定义一个函数(当然有一些限制),如果函数被“正常”调用,名称不会产生任何影响。
  • 但是,如果您使用 <- 后缀命名一个函数,它将成为一个替换函数,并允许解析器使用本答案开头描述的机制应用该函数,如果由语法 foo(x)<-value 调用。在这里看到,您没有明确调用 foo<-,但使用稍微不同的语法,您可以获得一个对象 replacement(因为名称)。
  • 虽然没有正式的限制,但在 R 中定义同名的 getter/setter 是很常见的(例如 namesnames<-)。在这种情况下,<-后缀函数是对应版本的无后缀替换函数。
  • 如开头所述,此行为是通用的,是语言的属性,因此无需在任何替换函数文档中讨论。
,

特别是,可以看到 names(x)<-y 实际上更改了 x 的名称,而 "names<-"(z,y) 返回 z 并更改了名称。

那是因为 `names<-`1 是一个常规函数,尽管名称很奇怪2。它不执行任何赋值操作,它返回一个带有 names 属性集的新对象。事实上,`names<-` 是 R 中的一个原始函数,但它可以实现如下(在 R 中有更短、更好的编写方法,但我希望单独的步骤是明确的) :

`names<-` = function (x,value) {
    new = x
    attr(new,'names') = value
    new
}

就是这样

  • ... 创建一个新对象,它是 x 的副本,
  • ... 在新创建的对象上设置 names 属性,并且
  • ... 返回新对象。

由于几乎所有 R 中的对象都是不可变的,这自然符合 R 的语义。事实上,这个确切函数的更好名称是 with_names3。但是 R 的创建者发现能够在不重复对象名称的情况下编写这样的赋值很方便。所以而不是写

x = with_names(x,c('foo','bar'))

x = `names<-`(x,'bar'))

R 允许我们写

names(x) = c('foo','bar')

R 通过在内部将其转换为另一个表达式来专门处理此语法,该表达式记录在 Subset assignment section of the R language definition 中,如 Nicola 的回答中所述。

但要点是 names(x) = y `names<-`(x,y) 是不同的,因为……它们就是。前者是一种特殊的句法形式,它被 R 解析器识别和转换。后者是一个常规函数调用,奇怪的函数名是一个红鲱鱼:它不影响执行。它的作用与函数的名称不同一样,您可以通过为其分配不同的名称来确认这一点:

with_names = `names<-`
`another weird(!) name` = `names<-`

# These are all identical:

`names<-`(x,y)
with_names(x,y)
`another weird(!) name`(x,y)

1 我强烈建议使用反引号 (`) 而不是直引号('")来引用 R 变量名称。虽然在某些情况下两者都被允许,但后者会与字符串混淆,并且在概念上是疯子。 这些不是字符串。考虑:

"a" = "b"
"c" = "a"

这段代码实际上不是将 a 的值复制到 c 中,而是将 c 设置为字面量 "a",因为引号现在在左侧表示不同的含义- 以及作业的右侧。

R documentation 确认

首选引号[变量名]是反引号 (`)

2 R 中的常规变量名称(又名“标识符”或“名称”)只能包含字母、数字、下划线和点,必须以字母,或后跟不带数字的点,并且不能是保留字。但是 R 允许使用几乎任意的字符——包括标点符号甚至空格! — 在变量名称中,前提是名称是反引号的。

3 事实上,R 为这个函数提供了一个几乎别名,叫做 setNames——这不是一个好名字,因为 set… 意味着改变对象,但它当然不会那样做。