Чем отличаются операторы присваивания «=» и «<-» в R?

713

Каковы различия между операторами присваивания =и <-в R?

Я знаю, что операторы немного отличаются, как показывает этот пример

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

Но единственная ли это разница?

csgillespie
источник
45
Как уже отмечалось здесь происхождение <-символа приходят от старых APL клавиатур , которые фактически имели один <-ключ на них.
Джоран

Ответы:

97

Каковы различия между операторами присваивания =и <-в R?

Как ваш пример показывает, =и <-имеют несколько иной приоритет оператора (который определяет порядок вычисления , когда они смешиваются в одном выражении). На самом деле, ?Syntaxв R выдает следующую таблицу приоритетов операторов, от высшего к низшему:

…
‘-> ->>’           rightwards assignment
‘<- <<-’           assignment (right to left)=’                assignment (right to left)

Но единственная ли это разница?

Поскольку вы спрашивали об операторах присваивания : да, это единственная разница. Тем не менее, вы были бы прощены за то, что поверили в обратное. Даже документация R ?assignOpsутверждает, что есть больше различий:

Оператор <-может использоваться где угодно, тогда как оператор =допускается только на верхнем уровне (например, в полном выражении, набранном в командной строке) или в качестве одного из подвыражений в ограниченном списке выражений.

Давайте не будем заострять внимание на этом: документация на R (слегка) неверна [ 1 ] . Это легко показать: нам просто нужно найти контрпример для =оператора, который не (а) не находится на верхнем уровне и не (b) является подвыражением в ограниченном списке выражений (то есть {…; …}). - Без дальнейших церемоний:

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

Ясно, что мы выполнили присваивание =вне контекста (a) и (b). Итак, почему документация о ядре языка R была неправильной в течение десятилетий?

Это потому, что в синтаксисе R символ =имеет два разных значения, которые обычно смешиваются:

  1. Первое значение как оператор присваивания . Это все, о чем мы говорили до сих пор.
  2. Второе значение - не оператор, а синтаксический маркер, который сигнализирует именованный аргумент, передаваемый в вызове функции. В отличие от =оператора, он не выполняет никаких действий во время выполнения, он просто изменяет способ анализа выражения.

Посмотрим.

В любой части кода общего вида ...

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

... =это токен, который определяет передачу именованного аргумента: это не оператор присваивания. Кроме того, =полностью запрещено в некоторых синтаксических контекстах:

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

Любое из них вызовет ошибку «неожиданно» = «в‹ bla ›».

В любом другом контексте =относится к вызову оператора присваивания. В частности, простое размещение скобок вокруг подвыражения делает любое из вышеперечисленного (а) действительным и (б) назначением . Например, следующее выполняет назначение:

median((x = 1 : 10))

Но и:

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

Теперь вы можете возразить, что такой код ужасен (и вы можете быть правы). Но я взял этот код из base::file.copyфункции (заменив <-на =) - это распространенный шаблон в большей части основной кодовой базы R.

Оригинальное объяснение Джона Chambers , которым документация по R, вероятно , на основе, на самом деле объясняет это правильно:

[ =присваивание] разрешено только в двух местах грамматики: на верхнем уровне (как полная программа или пользовательское выражение); и когда он изолирован от окружающей логической структуры, скобками или дополнительной парой скобок.


Признание: я солгал раньше. Там является одна дополнительная разница между =и <-операторами: они называют различные функции. По умолчанию эти функции делают то же самое, но вы можете переопределить любую из них отдельно, чтобы изменить поведение. В отличие от этого, <-и ->(назначение слева направо), хотя синтаксически различны, всегда вызывают одну и ту же функцию. Переопределение одного переопределяет другое. Знание этого редко бывает практичным, но его можно использовать для забавных махинаций .

Конрад Рудольф
источник
1
Что касается приоритета и ошибок в документации R, то приоритет ?фактически находится между ними =и <-, что имеет важные последствия при переопределении ? , и практически ни при чем другом.
Moody_Mudskipper
@Moody_Mudskipper это странно! Вы, кажется, правы, но согласно исходному коду ( main/gram.y), приоритет ?правильно задокументирован и ниже, чем оба, =и <-.
Конрад Рудольф
Я не говорю на C, но я полагаю, что =нужно пройти специальную обработку, прежде чем будет построено дерево разбора. Может быть, связано с аргументами функции, имеет смысл, что foo(x = a ? b)мы будем искать, =прежде чем анализировать остальную часть выражения.
Moody_Mudskipper
@Moody_Mudskipper Я спросил r-devel
Конрад Рудольф
2
@Moody_Mudskipper FWIW это наконец исправлено в 4.0.0.
Конрад Рудольф
661

Разница в операторах присваивания более очевидна, когда вы используете их для установки значения аргумента в вызове функции. Например:

median(x = 1:10)
x   
## Error: object 'x' not found

В этом случае xобъявляется в рамках функции, поэтому он не существует в рабочей области пользователя.

median(x <- 1:10)
x    
## [1]  1  2  3  4  5  6  7  8  9 10

В этом случае xобъявляется в рабочей области пользователя, поэтому вы можете использовать его после завершения вызова функции.


В сообществе R есть общее предпочтение использовать <-для назначения (кроме подписей функций) совместимость со (очень) старыми версиями S-Plus. Обратите внимание, что пробелы помогают прояснить такие ситуации, как

x<-3
# Does this mean assignment?
x <- 3
# Or less than?
x < -3

Большинство RE IDE имеют сочетания клавиш для <-упрощения ввода. Ctrl+ =в Architect, Alt+ -в RStudio ( Option+ -под macOS), Shift+ -(подчеркивание) в emacs + ESS.


Если вы предпочитаете писать =на <-но хотите использовать более общий символ присваивания для публично выпущенной коды (на CRAN, например), то вы можете использовать одну из tidy_*функций в formatRпакете для автоматической замены =с <-.

library(formatR)
tidy_source(text = "x=1:5", arrow = TRUE)
## x <- 1:5

Ответ на вопрос «Почему x <- y = 5выкидывает ошибку, а нет x <- y <- 5?» "Это зависит от магии, содержащейся в парсере". Синтаксис R содержит много неоднозначных случаев, которые должны быть решены так или иначе. Синтаксический анализатор выбирает разрешить биты выражения в различных порядках в зависимости от того , =или <-был использован.

Чтобы понять, что происходит, вам нужно знать, что назначение молча возвращает назначенное значение. Вы можете увидеть это более четко, например, явно печатая print(x <- 2 + 3).

Во-вторых, понятнее, если мы будем использовать префиксную нотацию для присваивания. Так

x <- 5
`<-`(x, 5)  #same thing

y = 5
`=`(y, 5)   #also the same thing

Парсер интерпретирует x <- y <- 5как

`<-`(x, `<-`(y, 5))

Мы можем ожидать, x <- y = 5что тогда будет

`<-`(x, `=`(y, 5))

но на самом деле это интерпретируется как

`=`(`<-`(x, y), 5)

Это связано с тем, что =приоритет меньше <-, чем показано на ?Syntaxстранице справки.

Ричи Коттон
источник
4
Это также упоминается в главе 8.2.26 « The Inferno » Патрика Бернса (в любом случае, не я, а рекомендация)
Уве,
3
Тем median((x = 1:10))не менее, имеет тот же эффект, что и median(x <- 1:10).
Франческо Наполитано
2
я действительно не считаю их ярлыками, в любом случае вы нажимаете одинаковое количество клавиш
yosemite_k
5
Я только что понял, что ваше объяснение того, как x <- x = 5его интерпретируют, немного неверно: в действительности R интерпретирует его как ​`<-<-`(x, y = 5, value = 5)(что само по себе более или менее эквивалентно tmp <- x; x <- `<-<-`(tmp, y = 5, value = 5)). Хлоп!
Конрад Рудольф
4
… И я только что понял, что самая первая часть этого ответа неверна и, к сожалению, вводит в заблуждение, потому что увековечивает распространенное заблуждение: способ, которым вы пользуетесь =при вызове функции , не выполняет присваивание и не является оператором присваивания. Это совершенно отличное синтаксическое выражение R, которое использует один и тот же символ. Кроме того, код, который вы показываете, не «объявляется» xв области действия функции. В объявлении функции выполняет указанное заявление. Вызов функции не делает (с именованными ...аргументами это немного усложняется ).
Конрад Рудольф
103

Руководство по стилю Google R упрощает проблему, запрещая "=" для назначения. Неплохой выбор.

https://google.github.io/styleguide/Rguide.xml

Руководство R подробно описывает все 5 операторов присваивания.

http://stat.ethz.ch/R-manual/R-patched/library/base/html/assignOps.html

Nosredna
источник
133
Недостаток случайного назначения, x<-yкогда x < -yподразумевалось, раздражает меня так сильно, что я лично предпочитаю =. Наличие вашего кода зависит от наличия пробелов, мне не кажется хорошим. Можно рекомендовать интервалы в качестве совета по стилю, но для того, чтобы ваш код работал по-другому, есть ли пробел или нет? Что, если вы переформатируете свой код или воспользуетесь поиском и заменой, пробельные символы могут иногда исчезать и код будет ошибочным. Это не проблема с =. IIUC, запрещающий =приравнивается к требованию " <- "; т.е. 3 символа, включая пробел, а не просто " <-".
Мэтт Доул
12
Обратите внимание, что любое значение, отличное от 0, рассматривается TRUER. Поэтому, если вы намереваетесь проверить, если xоно меньше -y, вы можете написать, if (x<-y)что не будет предупреждать или выдавать ошибку, и, похоже, будет работать нормально. Это будет только FALSEтогда y=0, когда.
Мэтт Доул
4
Если вы запрещаете =и используете <- , трудно утверждать, что дополнительный шаг grep "[^<]<-[^ ]" *.Rне требуется. =не нужно такого grep.
Мэтт Доул
34
Зачем болеть глаза и палец, <-если вы можете использовать =? В 99,99% случаев =это нормально. Иногда вам нужно, <<-хотя, это другая история.
Фернандо
10
Акцент на <- это, пожалуй, одна из слабых причин отсутствия + = и - =.
Крис
37

x = y = 5эквивалентно x = (y = 5), потому что операторы присваивания "группа" справа налево, который работает. Значение: присвоить 5 y, оставив число 5; а затем назначьте это 5x .

Это не то же самое (x = y) = 5, что не работает! Значение: присвойте значение yto x, оставив значениеy ; а затем назначить 5, хм ... что именно?

Когда вы смешиваете различные виды операторов присваивания, <-связываете жестче, чем =. Так x = y <- 5интерпретируется как x = (y <- 5), что имеет смысл.

К сожалению, x <- y = 5это интерпретируется как (x <- y) = 5, что не работает!

Смотрите ?Syntaxи ?assignOpsправила приоритета (привязки) и группировки.

Стив Питчерс
источник
Да, как сказал ответ Конрада Рудольфа,<- <<- выше = в таблице приоритетов, что означает, что <-будет выполнено в первую очередь. Итак, x <- y = 5должно быть выполнено как (x <- y) = 5.
Ник Донг
1
@ Ник Донг Да, действительно. Полезно, что таблица приоритетов операторов однозначно документирована в ? Синтаксис {base} .
Стив Питчерс
33

По словам Джона Чемберса, оператор =допускается только на «верхнем уровне», что означает, что он не допускается в управляющих структурах, например if, делает следующую ошибку программирования недопустимой.

> if(x = 0) 1 else x
Error: syntax error

Как он пишет, «Запрещение новой формы присваивания [=] в управляющих выражениях позволяет избежать ошибок программирования (таких как приведенный выше пример), которые более вероятны при использовании оператора равенства, чем при других присваиваниях S».

Вы можете сделать это, если он «изолирован от окружающей логической структуры, фигурными скобками или дополнительной парой скобок», так if ((x = 0)) 1 else xбудет работать.

Смотрите http://developer.r-project.org/equalAssign.html

Аарон оставил переполнение стека
источник
11
Это распространенная ошибка, x==0почти всегда подразумевается вместо этого.
Аарон оставил переполнение стека
14
Ах да, я упустил из виду, что вы сказали "ошибка программирования". Это на самом деле хорошая новость, что это вызывает ошибку. И веская причина, чтобы отдать предпочтение в x=0качестве задания над x<-0!
Стив Питчерс
7
Да, хорошо, что это вызывает ошибку, хотя я извлекаю другой урок о том, что предпочитать; Я выбираю использовать =как можно меньше , потому =и ==выглядят так похожи.
Аарон оставил переполнение стека
2
То, как этот пример представлен, мне так странно. if(x = 0) 1 else xвыдает ошибку, помогая мне найти и исправить ошибку. if(x <- 1) 1 else xне выдает ошибку и очень сбивает с толку.
Грегор Томас
3
Я имею в виду, что действительно полезная программа проверки ошибок выдаст ошибку и скажет: «У вас есть бесполезный код, который всегда будет возвращать elseзначение, вы хотели написать его таким образом?», Но это может быть несбыточной мечтой ...
TylerH
26

Операторы <-и =назначают в среду, в которой они оцениваются. Оператор <-может использоваться где угодно, тогда как оператор =допускается только на верхнем уровне (например, в полном выражении, набранном в командной строке) или в качестве одного из подвыражений в ограниченном списке выражений.

Хаим Евги
источник
8
Я думаю, что «верхний уровень» означает на уровне выражения, а не на уровне выражения. Так что само x <- 42по себе это утверждение; в if (x <- 42) {}нем было бы выражение, и оно недействительно. Чтобы было ясно, это не имеет никакого отношения к тому, находитесь ли вы в глобальной среде или нет.
Стив Питчерс
1
Это: «оператор = разрешен только на верхнем уровне» является широко распространенным недоразумением и совершенно ошибочным.
Конрад Рудольф
Это не так - например, это работает, хотя назначение не является полным выражением:1 + (x = 2)
Павел Минаев
1
Чтобы прояснить комментарии КонрадРудольфа и Павла Минаева, я думаю, что слишком убедительно говорить, что это совершенно неправильно, но есть исключение, которое происходит, когда он «изолирован от окружающей логической структуры скобками или дополнительной парой скобок».
Аарон оставил переполнение стека
Или function() x = 1, repeat x = 1, if (TRUE) x = 1....
Moody_Mudskipper
6

Это также может добавить к пониманию разницы между этими двумя операторами:

df <- data.frame(
      a = rnorm(10),
      b <- rnorm(10)
)

Для первого элемента R присвоены значения и собственное имя, тогда как имя второго элемента выглядит несколько странно.

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 ...

Версия R 3.3.2 (2016-10-31); macOS Sierra 10.12.1

Денис Расулев
источник
6
Можете ли вы дать более подробное объяснение того, почему это происходит / что здесь происходит? (подсказка: data.frameпытается использовать имя предоставленной переменной в качестве имени элемента во фрейме данных)
Бен Болкер,
Просто подумал, может ли это быть ошибкой? И если да, то как и где мне сообщить об этом?
Денис Расулев
7
это не ошибка Я попытался намекнуть на ответ в моем комментарии выше. При установке имени элемента, R будет использовать эквивалент make.names("b <- rnorm(10)").
Бен Болкер