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

Why are names(x)<-y and "names<-"(x,y) not equivalent?

考虑以下几点:

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) returns z 的名称已更改。

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

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

For example, names(x) <- c("a","b") is equivalent to:

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

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

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

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

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

In particular, one sees that names(x)<-y actually changes the names of x whereas "names<-"(z,y) returns z with its names changed.

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

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

也就是它

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

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

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

x = `names<-`(x, c('foo', '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 确认

The preferred quote [for variable names] is the backtick (`)

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

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