在R中赋值运算符=和<-之间有什么区别?

我知道操作符略有不同,如本例所示

x <- y <- 5
x = y = 5
x = y <- 5
x <- y = 5
# Error in (x <- y) = 5 : could not find function "<-<-"

但这是唯一的不同吗?


当前回答

在R中赋值运算符=和<-之间有什么区别?

如示例所示,=和<-的运算符优先级略有不同(当它们混合在同一个表达式中时,运算符优先级决定了求值的顺序)。事实上,R中的?语法给出了以下操作符优先级表,从高到低:

... ' -> ->> '向右分配 ' <- <<- '赋值(从右向左) ' = '赋值(从右到左) ...

但这是唯一的不同吗?

既然你问的是赋值操作符:是的,这是唯一的区别。然而,如果你不这么认为,也情有可原。甚至?assignOps的R文档也声称有更多的区别:

操作符<-可以在任何地方使用, 而运算符=只允许在顶层(例如, 在命令提示符处输入的完整表达式中)或作为一个 一个带括号的表达式列表中的子表达式。

让我们不要过分强调它:R文档是错误的。这很容易显示:我们只需要找到一个=运算符的反例,它既不是(a)在顶层,也不是(b)在一个带括号的表达式列表中的子表达式(例如{…;})。-闲话不多说:

x
# Error: object 'x' not found
sum((x = 1), 2)
# [1] 3
x
# [1] 1

显然,我们已经在上下文(a)和(b)之外执行了赋值,使用=。那么,为什么R语言核心特性的文档几十年来一直是错误的呢?

这是因为在R的语法中,符号=有两个不同的含义,经常被混为一谈(甚至是专家,包括在上面引用的文档中):

第一个含义是赋值操作符。这是我们目前为止讨论的全部内容。 第二个含义不是一个操作符,而是一个语法标记,表示在函数调用中传递的命名参数。与=运算符不同,它在运行时不执行任何操作,它只是改变表达式的解析方式。

那么R如何决定=的给定用法是指操作符还是指定参数传递?让我们来看看。

在任何一段一般形式的代码中…

‹function_name›(‹argname› = ‹value›, …)
‹function_name›(‹args›, ‹argname› = ‹value›, …)

…the =是定义命名参数传递的令牌:它不是赋值操作符。此外,=在某些句法环境中是完全禁止的:

if (‹var› = ‹value›) …
while (‹var› = ‹value›) …
for (‹var› = ‹value› in ‹value2›) …
for (‹var1› in ‹var2› = ‹value›) …

任何这些都会引发错误“unexpected”=“in”。

在任何其他上下文中,=指赋值操作符调用。特别是,只要在子表达式周围加上括号,就可以使上面的(a)有效,(b)为赋值。例如,下面执行赋值:

median((x = 1 : 10))

但也:

if (! (nf = length(from))) return()

现在您可能会反对说这样的代码很糟糕(您可能是对的)。但是我从base::文件中获取了这段代码。复制函数(用=替换<-)——这是R核心代码库中普遍存在的模式。

约翰·钱伯斯(John Chambers)最初的解释,也就是R文档可能基于的解释,实际上正确地解释了这一点:

[=赋值]只允许出现在语法中的两个地方:在顶层(作为一个完整的程序或用户类型的表达式);当与周围的逻辑结构隔离时,通过大括号或额外的一对括号。


总的来说,默认情况下,操作符<-和=做同样的事情。但它们中的任何一个都可以被单独覆盖以改变其行为。相比之下,<-和->(从左到右赋值)虽然语法不同,但总是调用相同的函数。覆盖一个也会覆盖另一个。知道这一点很少是实际的,但它可以用于一些有趣的恶作剧。

其他回答

来自官方R文档:

操作符<-和=赋值到它们所处的环境 进行了评估。操作符<-可以在任何地方使用,而 操作符=只允许在顶层(例如,在 在命令提示符处键入的完整表达式)或作为 带括号的表达式列表中的子表达式。

我不确定Patrick Burns的书R inferno是否在8.2.26中被引用,其中=不是<的同义词- Patrick说“当你想设置一个函数的参数时,你显然不想使用'<-'。”这本书可以在https://www.burns-stat.com/documents/books/the-r-inferno/上买到

在R中赋值运算符=和<-之间有什么区别?

如示例所示,=和<-的运算符优先级略有不同(当它们混合在同一个表达式中时,运算符优先级决定了求值的顺序)。事实上,R中的?语法给出了以下操作符优先级表,从高到低:

... ' -> ->> '向右分配 ' <- <<- '赋值(从右向左) ' = '赋值(从右到左) ...

但这是唯一的不同吗?

既然你问的是赋值操作符:是的,这是唯一的区别。然而,如果你不这么认为,也情有可原。甚至?assignOps的R文档也声称有更多的区别:

操作符<-可以在任何地方使用, 而运算符=只允许在顶层(例如, 在命令提示符处输入的完整表达式中)或作为一个 一个带括号的表达式列表中的子表达式。

让我们不要过分强调它:R文档是错误的。这很容易显示:我们只需要找到一个=运算符的反例,它既不是(a)在顶层,也不是(b)在一个带括号的表达式列表中的子表达式(例如{…;})。-闲话不多说:

x
# Error: object 'x' not found
sum((x = 1), 2)
# [1] 3
x
# [1] 1

显然,我们已经在上下文(a)和(b)之外执行了赋值,使用=。那么,为什么R语言核心特性的文档几十年来一直是错误的呢?

这是因为在R的语法中,符号=有两个不同的含义,经常被混为一谈(甚至是专家,包括在上面引用的文档中):

第一个含义是赋值操作符。这是我们目前为止讨论的全部内容。 第二个含义不是一个操作符,而是一个语法标记,表示在函数调用中传递的命名参数。与=运算符不同,它在运行时不执行任何操作,它只是改变表达式的解析方式。

那么R如何决定=的给定用法是指操作符还是指定参数传递?让我们来看看。

在任何一段一般形式的代码中…

‹function_name›(‹argname› = ‹value›, …)
‹function_name›(‹args›, ‹argname› = ‹value›, …)

…the =是定义命名参数传递的令牌:它不是赋值操作符。此外,=在某些句法环境中是完全禁止的:

if (‹var› = ‹value›) …
while (‹var› = ‹value›) …
for (‹var› = ‹value› in ‹value2›) …
for (‹var1› in ‹var2› = ‹value›) …

任何这些都会引发错误“unexpected”=“in”。

在任何其他上下文中,=指赋值操作符调用。特别是,只要在子表达式周围加上括号,就可以使上面的(a)有效,(b)为赋值。例如,下面执行赋值:

median((x = 1 : 10))

但也:

if (! (nf = length(from))) return()

现在您可能会反对说这样的代码很糟糕(您可能是对的)。但是我从base::文件中获取了这段代码。复制函数(用=替换<-)——这是R核心代码库中普遍存在的模式。

约翰·钱伯斯(John Chambers)最初的解释,也就是R文档可能基于的解释,实际上正确地解释了这一点:

[=赋值]只允许出现在语法中的两个地方:在顶层(作为一个完整的程序或用户类型的表达式);当与周围的逻辑结构隔离时,通过大括号或额外的一对括号。


总的来说,默认情况下,操作符<-和=做同样的事情。但它们中的任何一个都可以被单独覆盖以改变其行为。相比之下,<-和->(从左到右赋值)虽然语法不同,但总是调用相同的函数。覆盖一个也会覆盖另一个。知道这一点很少是实际的,但它可以用于一些有趣的恶作剧。

X = y = 5等价于X = (y = 5),因为赋值操作符从右向左“分组”,这是可行的。含义:将5赋给y,保留数字5;然后把5赋值给x。

这和(x = y) = 5不一样,不成立!含义:将y的值赋给x,保留y的值;然后把5赋给,嗯…,到底是什么?

当混合使用不同类型的赋值操作符时,<-比=绑定得更紧。所以x = y <- 5可以解释为x = (y <- 5)这样就说得通了。

不幸的是,x <- y = 5被解释为(x <- y) = 5,这种情况是行不通的!

有关优先级(绑定)和分组规则,请参阅?Syntax和?assignOps。

在R的过去版本甚至R的前身语言(S语言)中,<-和=之间存在一些差异。但目前,像使用其他现代语言(python, java)一样使用=似乎不会引起任何问题。你可以通过使用<-来实现更多的功能,当传递一个值给一些增强,同时也创建一个全局变量,但它可能会有奇怪的/不想要的行为,如in

df <- data.frame(
      a = rnorm(10),
      b <- rnorm(10)
)
str(df)
# 'data.frame': 10 obs. of  2 variables:
#  $ a             : num  0.6393 1.125 -1.2514 0.0729 -1.3292 ...
#  $ b....rnorm.10.: num  0.2485 0.0391 -1.6532 -0.3366 1.1951 ...

强烈推荐!试着阅读这篇文章,这是试图解释这两者区别的最好的文章: 检查https://colinfay.me/r-assignment/

此外,可以将<-看作一个无形地返回一个值的函数。

a <- 2
(a <- 2)
#> [1] 2

参见:https://adv-r.hadley.nz/functions.html