Стилевой гид это не просто негласный договор разработчиков – за многими из правил стоит любопытная предыстория. Почему стрелка
<-
лучше знака равенства =
, почему старожилы R не любят нижнее подчеркивание, как рекомендуемая длина строки связана с перфокартой, и о многом другом – далее.Тем не менее, гид Хэдли можно считать самым распространённым (как встроенная проверка RStudio, например), чему в немалой степени способствует популярность библиотек, созданных самим Хэдли (dplyr, ggplot, tidyr, и других из коллекции tidyverse).
1. Оператор присваивания: <-
vs =
Все доступные гиды рекомендуют использовать нестандартный оператор
<-
, а вовсе не знак равенства =
, привычный для других современных языков. Три других оператора (<<-
, ->
, ->>
) даже не упоминаются (как и существовавший в ранних версиях :=
). Казалось бы, зачем нужна эта нестандартная стрелка?История приоткрывает нам карты: в R стрелка пришла из S, который в свою очередь, унаследовал её от APL. В языке APL она позволяла отличить присвоение от равенства. В R оператор равенства стандартный, так что разница в другом. Если стрелка была оператором присвоения изначально, то знак равенства присваивал значения только именованным параметрам. В 2001 году знак равенства стал оператором присвоения, но так и не стал синонимом стрелки.
Что же позволяет считать
=
полноценной заменой стрелке? Прежде всего, =
как оператор присвоения работает только на верхнем уровне. Например, внутри функции всё будет работать по-старому:mean(x = 1:5)
# [1] 3
x
# Error: object 'x' not found
mean (x <- 1:5)
# [1] 3
x
# [1] 1 2 3 4 5
Здесь
=
только задаёт параметр функции, в то время как <-
ещё и присваивает значение переменной x. Мы можем добиться такого же эффекта, поместив операцию присваивания в скобки mean ((x = 1:5))
# [1] 3
x
# [1] 1 2 3 4 5
… или в фигурные скобки:
mean ({x = 1:5})
# [1] 3
x
# [1] 1 2 3 4 5
Помимо этого, стрелка имеет приоритет над знаком равенства:
x <- y <- 1
# OK
x = y = 2
# OK
x = y <- 3
# OK
x <- y = 4
# Error in x <- y = 4 : could not find function "<-<-"
Последнее выражение завершилось ошибкой потому что эквивалентно
(x <- y) = 4
, и парсер интерпретирует его как`<-<-`(x, y = 4, value = 4)
Иными словами, мы пытаемся выполнить некорректную операцию: сначала присваиваем x значение y, а затем пытаемся присвоить x и y значение 4. Выражение будет обработано без ошибок только если поменять приоритет операций скобками:
x <- (y = 4)
.2. Spacing
Гид рекомендует ставить пробелы между операторами (кроме, естественно, квадратных скобок, :, :: и :::), а также перед открывающей скобкой. Вполне очевидно, что это часть стандартов кодирования GNU. Впрочем, этот пункт тесно связан с использованием
<-
как оператора присвоения. Например,x <-1
Что это? X меньше -1? Или присвоить x значение 1?
Впрочем, лишний пробел ничуть не лучше недостающего, например:
x <- 0
ifelse(x <-1, T, F)
# [1] TRUE
x <- 0
ifelse(x < -1, T, F)
# [1] FALSE
В первом случае нет пробела между
<
и -
, что создаёт оператор присваивания.3. Имена функций и переменных
В вопросе имён стилевые руководства расходятся: гид Хэдли рекомендует нижнее подчёркивание для всех имен; Гид Google – разделение точками для переменных и стиль верблюда с первой строчной для функций; Bioconductor рекомендует lowerCamel как для функций, так и для переменных. В этом вопросе в сообществе R нет единства, и можно встретить все возможные стили:
lowerCamel
period.separation
lower_case_with_underscores
allowercase
UpperCamel
Единого стиля нет даже именах base R (например, rownames и row.names это разные функции!). Если не брать во внимание нечитабельный allowercase (его могут любить только пользователи Matlab), можно выделить три самых популярных стиля: lowerCamel, нижний регистр с _, и нижний регистр с разделением точками.
Популярность разных стилей для названий функций и параметров (одно название может отвечать разным стилям). Источник: выступление Rasmus Baath на useR!2017.
Разделение точками зловеще напоминает использование методов в объектно-ориентированном программировании, но исторически распространено. Настолько распространено, что именно этот стиль можно считать истинно R’вским. Например, большинство базовых функций используют именно его (а с data.table и as.factor точно встречался каждый).
А вот разделение _ является одним из наименее популярных стилей (и здесь Хэдли идёт против большинства). Для многих пользователей R нижнее подчёркивание будет раздражителем: в популярном расширении Emacs Speaks Statistics оно по умолчанию заменяется на оператор присвоения
<-
. А настройки по умолчанию, конечно же, Впрочем, влияние Emacs ESS – это всё ещё объяснение из разряда «хвост виляет собакой». Есть и более древняя причина: в ранних версиях R нижнее подчёркивание было синонимом стрелки
<-
. Например, в 2000 году можно было встретить такое:# имело место в ранних версиях R
c <- c(1,2,3,4,5)
mean(c)
[1] 3
c_mean <- mean(c)
c
[1] 3
Здесь вместо того, чтобы создать переменную
c_mean
R присвоил значение 3 сначала переменной mean, а затем переменной c. В современном R такие метаморфозы, конечно же, происходить не будут.Из-за непопулярности _ функции такого стиля почти не встречаются среди базовых:
# в 3.5.1 всего 25 функций
grep("^[^\\.]*$", apropos("_"), value = T)
Наконец, стиль lowerCamel отличается низкой читабельностью при использовании длинных имён:
# ой!
GrossNationalIncomePerCapitaAtlasMethodCurrentUnitedStatesDollars
Таким образом, в плане имён рекомендации гида нельзя считать однозначными; всё-таки это дело вкуса (до тех пор пока в этом наблюдается последовательность).
4. Фигурные скобки
Согласно гиду, после открывающей фигурной скобки должна следовать новая строка, а закрывающая находиться на отдельной строке (если только за ней не следует else). Т.е. примерно так:
if (x >= 0) {
log(x)
} else {
message("Not applicable!")
}
Здесь всё не очень интересно: это стандартный стиль отступов K&R, восходящий к языку C и знаменитой книге Кернигана и Ритчи «The C Programming Language» (или K&R по именам авторов).
Истоки этого стиля тоже вполне очевидны: он позволяет экономить строки, сохраняя при этом читабельность. Для ранних компьютеров вертикальное пространство было слишком большой роскошью. Например, C был разработан на PDP-11, в терминале которого было всего 24 строки. Да и при печати книги K&R этот стиль экономил бумагу!
5. Строка на 80 символов
Рекомендуемая длина строки согласно гиду – 80 символов. Волшебное число 80 встречаются не только в R, но и огромном количестве других языков (Java, Perl, PHP, и т.д. и т.п.). И не только языков: даже командная строка Windows состоит из 80 символов.
Впервые в программировании это число появилось в 1928 г. вместо со стандартной перфокартой IBM, где было ровно 80 колонок для данных. Куда более интересный вопрос – почему был выбран именно такой стандарт? Ведь ранее использовались и перфокарты другой длины (на 24 или 45 колонок).
Самый популярный ответ связывает длину перфокарты с длиной строки у печатных машинок. Первые машинки были рассчитаны на американский стандарт бумаги 8? x 11 дюймов, и позволяли печатать от 72 до 90 символов, в зависимости от размера полей. Поэтому версия о 80 символах в строке выглядит вполне правдоподобной, хотя и не правдой в последней инстанции. Вполне возможно, что 80 символов – это просто золотая середина в плане эргономики.
6. Отступ строки: пробелы vs табуляция
Рекомендуемый гидом стиль – это два пробела, а не табуляция. Отказ от табуляции вполне понятен: длина TAB’а варьируется в разных текстовых редакторах (это может быть что угодно от 2 до 8 пробелов). Отказываясь о них, мы получаем сразу два преимущества: во-первых, код будет выглядеть точно также, как мы его набрали; во-вторых, не произойдёт случайного нарушения рекомендуемой длины строки. При этом мы, конечно же, увеличиваем размер файла (кто хочет заниматься такой микрооптимизаций в 2k19 году?)
Спор пробелы vs табуляция имеет давнюю историю, и может приравниваться к религиозным (как, например, Win vs Linux, Android vs iOS, и тому подобные). Впрочем, мы уже знаем, кто в нём победил: согласно исследованию Stack Overflow, разработчики, использующие пробелы, зарабатывают больше, чем те, кто использует табуляцию. Более весомый аргумент, чем правила стилевого гида, не так ли?
Вместо заключения: правила стилевых гидов могут казаться странными и нелогичными. Действительно, зачем стрелка
<-
, если есть стандартный оператор =
? Но если копнуть глубже, то за каждым правилом стоит некоторая логика, часто уже подзабытая. Комментарии (8)
hombre
02.06.2019 15:11Из-за непопулярности _ функции такого стиля почти не встречаются среди базовых:
Да и мучительно привыкать к написанию seq_along()
с оператором %>% тоже регулярно путаница в dplyr и tidyverse
SegreyBorovkov
02.06.2019 16:46Объясните, пожалуйста, в чем смысл использования
<-
вместо=
? По мне — написать что-то типаfunc_name(x <- 5)
— хороший повод получить канделябром от коллеги, ибо не особо очевидно зачем так делать. А если так не делать, то почему не использовать повсеместно просто знак равенства?
Если посмотреть кернелы кагла, видно, что там R — в районе 5-10%. Для меня это показатель, что человек, занимающийся анализом данных на R и не желающий оказаться позади, должен как минимум понимать python. А если так — почему не использовать знак=
для присвоения и в R, раз он работает в обоих языках?demche Автор
02.06.2019 18:34В самом начале это давало обратную совместимость с S и S-PLUS. Сейчас особого смысла здесь нет: исключительно традиция. И поскольку R используется не только в машинном обучении, традиция вряд ли поменяется в обозримом будущем.
И да, внутри функции всё-таки лучше использовать=
. Просто этот пример наглядно демонстрирует, что назначение параметра функции и присвоение значения — не одно и тоже. В вашем примереfunc_name(x <- 5)
будет обработано всегда, а вотfunc_name(x = 5)
только если x является параметром функции.Cekory
02.06.2019 23:36Не совсем понимаю, что значит "
x <- 5
будет обработано всегда". Присваивание с одновременной передачей параметра снижает читаемость и поэтому считается плохим тоном, его надо избегать. А вариант сx = 5
упадет с ошибкой, еслиx
не является параметром функции и это очень правильно. Сам я всегда стараюсь писать=
, потому что никаких рациональных доводов в пользу стрелочки нет, только традиции. Против неё описанная вами ситуация со сравнениемx<-5
. То есть, с моей точки зрения, 1:0 в пользу=
.demche Автор
03.06.2019 00:06Будет обработано всегда — в смысле, что внутри функции мы можем присвоить переменной какое угодно имя, и ошибки не будет. Например,
mean(some_var_name <- 1:5)
будет обработано корректно, аmean(some_var_name = 1:5)
выдаст ошибку т.к. название аргумента у этой функции х.
(Сам не одобряю практику использования стрелки внутри функции, просто как иллюстрация возможности)Cekory
03.06.2019 00:12С моей точки зрения корректное поведение — выдача ошибки. Допустим, есть такая функция:
f = function(x = NA, y = NA) {cat("x = ", x, "y = ", y, "\n")}
, тогдаf(y <- 25)
выдастx = 25 y = NA
. Понятно, почему так происходит, но это явно не про читаемость.
BkmzSpb
Честно говоря, особенности
R
проявляются только в части пунктов. Расположение фигурных скобок, спейсинг и пробелы vs табуляция порождают конфликты вокруг, кажется, любого языка. Но хотелось бы добавить несколько вещей.1) Псевдооператор
`:=`
из пакетов семестваtidy
. Вызов оператора отваливается с ошибкой, но он по-особому интерпретируется механизмом цитированияrlang
и по сути означает присваивание:2) По-хорошему, использование оператора
<-
в списке аргументов функций должено считаться плохой практикой.<-
используется для присваивания значений в текущем окружении, поэтому проблемы можно получить в редком случае, когдаx <- 5
записан какx < -5
. К сожалению, синтаксис валидный и ошибки инетрпретации вы не получите. В качестве альтернативы можно окунуться в функциональный подход. Тогда присваивание будет последним действием:x %>% do_job %>% do_more(with_param) -> y
, но с точки зрения читабельности возникают вопросы.3) Имена функций и переменных. Честно говоря, базовые пакеты, написанные статистиками, это какой-то ад. Конвенции попросту отсутствуют, а запомнить какие-то особые аргументы просто невозможно (мне вспоминается дурацкий
na.rm
, который я вечно пишу какrm.na
). Имена с точками, которые есть и в базовом пакете, могут привести к проблемам с дженериками, т.к..class_name
постфикс используется для указания типа. Например, стандартныйdata.frame
имеет оператор преобразования as:as.data.frame
, в соответствии с соглашением обis
иas
операторах. Так вот, если применить этот метод кdata.frame
, то на деле вызовется…as.data.frame.data.frame
, и это валидная существующая функция, которую вы можете проверить.Собственно это одна из причин, по которой
tidyverse
использует lowercase с underscore.demche Автор
С этим полностью согласен: глобального присвоения следует избегать. Поэтому оправданной будет конвенция:=
был оператором присвоения только в ранних версиях R. Сейчас в base такого оператора больше нет, а значит его вызов и не должен работать. Но токен:=
остался в коде R какLEFT_ASSIGN
(см. source code).А раз есть токен, то
:=
можно использовать как инфиксный оператор, не прибегая к формату%function_name%
. Таким образом, его использование в коллекции tidy правомерное, хотя это своего рода хак. Да и не только там:=
встречается: в data.table он тоже есть.=
для параметров функции,<-
для всего остального.