История языка

S и Bell Labs

Рассказывать историю возникновения и современного состояния R без упоминания языка S невозможно. Строго говоря, R — это диалект S, так как полностью наследует идеологию и конструкцию языка, вплоть до обратной совместимости базовых выражений.

Язык S был создан в отделе статистики компании AT&T в её исследовательском подразделении Bell Labs в 1976 году. В то время в Bell Labs сложилась удивительная атмосфера: например, там работал Джон Тьюки (John Tukey) — создатель множества различных статистических критериев и в целом концепции разведочного анализа данных. Точно так же параллельно с S создавалась операционная система Unix, несколько лет ранее был создан язык C, а немногим позднее и С++. Не говоря уже о том, что в 1977 и в 1978 годах сотрудники Bell Labs получили Нобелевские премии по физике.

Концепцию языка разработали Джон Чамберс (John Chambers) — визуализации, работа с данными, алгоритмы, Рик Бекер (Rick Becker) — визуализации, система для анализа временных рядов, которую он ранее разрабатывал для Национального бюро экономических исследований США), Дуглас Данн (Douglas Dunn) — временные ряды, Пол Тьюки (Paul Tukey) с опытом в языке APL, а также Грэхэм Вилкинсон (Graham Wilkinson) — временный сотрудник Bell Labs, работал с системой для анализа сельскохозяйственных данных Genstat. В реализации первой версии языка также участвовали Жан Мак-Рае (Jean McRae) и Джуди Шиллинг (Judy Schilling). Основной идеолог и фактически автор S — Джон Чамберс, ссылки на книги которого можно регулярно видеть в справке R.

Первая версия языка была призвана упростить работу со статистической библиотекой, написанной на Fortran (SCS, Statistical Computing Subroutines). В работе группы регулярно возникали ситуации, когда требовалась исследовательская работа, взаимодействие с данными: попробовать модель, посмотреть на результат, что-то изменить и т. д. Рутинные операции, которые не требовали бы участия пользователя, встречались реже. Это привело к идее REPL (read-eval-print loop, цикл “чтение — вычисление — вывод”) и в целом интерактивной среды программирования. То есть такой среды, в которой есть интерактивный интерпретируемый язык, вызывающий Fortran-рутины. Язык S и впоследствии R в первую очередь ориентированы именно на интерактивную работу и восприятие человеком результатов анализа. С одной стороны, это действительно очень удобно в исследовательской работе. С другой стороны, в настоящее время среди пользователей R всё сильнее потребность в инструментах, которые наоборот не предполагают участия человека (выполнение скриптов на сервере, веб-серверы, бэкенды дашбордов и прочие задачи). К тому же первоначальная ориентация на то, что вывод результатов анализа будет читать человек, породила несколько причудливые особенности некоторых базовых функций. Например, функции print(), где выведенное в консоль значение может иметь меньше знаков после запятой, чем реальное число.

Название языка косвенно отражает статистическую направленность: было несколько вариантов названия, такие как Interactive SCS, Statistical Computing System, Statistical Analysis System и подобные. В результате, воспользовавшись прецедентом, решили последовать примеру языка C и назвать новый язык также одной буквой, которая встречалась во всех этих предложенных названиях. Так появилось название “S”.

Вторая версия S возникла в начале 1980-х годов, когда появилось желание распространять и использовать S не только в Bell Labs, но и в университетах и других исследовательских группах. Первая версия была ограничена операционной системой Honeywell GCOS, которая использовалась в Bell Labs и совсем не пользовалась популярностью среди разработчиков S. В результате вторая версия была портирована на разработанную почти в то же время операционную систему Unix. Одновременно было сделано достаточно много внутренних изменений в языке. В частности, в добавление к первоначальной векторизации ввели явные циклы for, тогда же появилась функция apply().

Третья версия языка появилась уже в конце 1980-х, и она больше всего похожа на то, что мы сейчас видим в базовом R. Вместо разнородных макросов появилась концепция (и тут явно чувствуется влияние языка Scheme), что всё является объектом языка — и функции, и выражения. Функции и классы были организованы в систему S3, хотя и сейчас можно увидеть анахронизмы в виде matrix (вектор с атрибутами, но по факту не S3-объект, а базовый класс). При этом весь язык был переписан на C — это значит, что почти все функции вызывали C, а Fortran-рутины остались лишь в некоторых пакетах. Другое сильное изменение — S несмотря на статистическую направленность в первых версиях во многом был популярен за счёт возможностей манипуляции с данными и визуализации. В третьей версии, наоборот, появились каноничные статистические функции (в частности, регрессии), а Чамберс и Хасти выпустили книгу “Statistical Models in S”, которая описывает и в какой-то мере документирует возможности статистического анализа в S, в том числе язык формул.

В начале девяностых (в 1993 году) лицензию на S купила компания Insightful Corporation и стала продавать под названием S-Plus. Любопытно, что позднее, уже в 2008 году, компанию Insightful Corporation купила TIBCO, которая также разрабатывает свою версию компилятора R (TERR).

Четвёртая версия S появилась уже в 1998 году, после создания R (в 1996 году). Это последняя на настоящий момент версия. Её ключевая особенность — появление S4, новой модели объектно-ориентированного программирования с более строгим определением классов и множественным наследованием.

R, R Core и R Foundation

Первые идеи и наработки новой системы для статистических вычислений появились в 1991 году в департаменте статистики Университета Окленда, Новая Зеландия. Роберт Джентельмен (Robert Gentleman), который приехал на несколько месяцев из Канады с курсом лекций, и Росс Ихака (Ross Ihaka) сошлись на любви обоих к языку Scheme и создали в учебной лаборатории самую первую версию R. Одним из основных мотивов создания собственной системы (позднее R уже стали называть языком) послужила проприетарность S. Однобуквенное обозначение сохраняло традицию наименования, а сама буква R была выбрана как общая буква имён авторов.

Язык R является диалектом S с рядом отличий. Правда, по словам Ихаки, изначально R был больше похож на небольшой интерпретатор Scheme, а сходство с S росло постепенно. Отход от Scheme был вызван слабой верой что Джентельмена, что Ихаки в возможности написать пригодный к использованию инструмент на Sсheme-подобном языке. Позднее, где-то в 2008–2010 годах, Ихака, видимо, разочаровался в R и вернулся к первоначальной идее, начав разрабатывать новый лиспоподобный, как и Scheme, язык.

Одно из ключевых (помимо формальных вещей вроде проприетарности) отличий R от S — организация работы с памятью. В S все объекты хранятся на диске, то есть при создании объекта происходит его запись на диск. Это даёт определённую надёжность при сбоях системы, однако сильно замедляет работу. В R все объекты хранятся в оперативной памяти, определенный лимит которой резервируется при старте сессии и потом управляется с помощью сборщика мусора.

Так как все объекты в S хранятся на диске, те из них, которые встречаются в теле функции в S, должны быть объявлены либо в глобальном окружении и переданы в значение аргументов функции, либо объявлены в теле функции. При этом возникают сложности в обработке объектов, которые используются в теле функции, но не указаны в аргументах (например, во вложенных функциях). Классический пример из документации R, когда в теле функции используется объект n, не созданный в теле функции и не переданный в аргументах:

cube <- function(n) {
  sq <- function() n * n
  n * sq()
}

В R подобная ситуация решается методами лексического связывания (подобно Scheme), то есть объекты, если они не указаны в аргументах, берутся из окружения, в котором была объявлена функция. В примере объект n отсутствует в аргументах функции sq() и берётся из локального окружения функции cube(). В S такая логика невозможна, и для корректной работы необходимо в глобальном окружении или окружении функции cube() задать n. Притом в выражении n * n будет использоваться именно это значение n, независимо от того, что было передано в аргументы cube().

Помимо лексического связывания и работы с памятью, R отличается от S возможностью упрощённого написания замыканий, разными методами работы с графикой, нотацией формул линейных моделей, рядом функций базовых пакетов и прочими не столь значительными нюансами. С некоторой долей иронии и лукавства можно сказать, что некоторые версии R различаются между собой чуть ли не сильнее (например, до и после введения встроенной JIT-компиляции или разных методов подсчёта ссылок на объекты в памяти).

В 1993 году R был публично представлен в рассылке S-сообщества, авторы получили обратную связь. Один из пользователей рассылки, Мартин Малер (Martin Maechler) убедил Джентельмена и Ихаку, планировавших коммерциализировать свои наработки, распространять и развивать R как free software. В 1995 году исходники R выложили в открытый доступ.

После этого развитие языка ускорилось: в 1997 году на базе Швейцарской высшей технической школы Цюриха (благодаря Мартину Малеру) были созданы рассылки r-announce, r-help и r-devel (действуют до сих пор, также добавились еще r-package и r-package-devel). Примерно тогда же оформилась R Core Team — группа разработчиков, которая занимается ядром языка, его развитием и документацией. В группу входит около двадцати разработчиков, многие из них в группе с самого начала. Один из постоянных членов — Джон Чамберс, также в развитии R в своё время приняли участие Рик Бекер и Тревор Хасти.

Первая стабильная версия (1.0) была выпущена 29 февраля 2000 года. Последующие версии выходят примерно раз в год в феврале-марте, иногда происходят релизы осенью.

В 2002 году была организована некоммерческая организация R Foundation, официальные цели которой — поддержка и развитие языка R, взаимодействие с сообществом и юридическое лицо для взаимодействия с организациями и спонсорами. Наиболее явное проявление деятельности организации — поддержка портала r-project.org и CRAN, а также гранты для локальных R user group, поддержка конференций. Почти всё руководство организации — члены R Core Team или разработчики ключевых для R-экосистемы пакетов (например, Дирк Эдельбюттель, создатель пакета Rcpp), юридически организация располагается в Вене (Австрия) на базе Венского экономического университета.

Подобная централизация позволяет выдерживать определённые стандарты качества кода и пакетов (так как пакеты в CRAN модерируются), само наличие CRAN существенно упрощает взаимодействие пользователей с экосистемой. Тем не менее, есть и обратная сторона, в немалой степени вызванная малой численностью R Core Team и весьма почтенным возрастом её членов: Питер Дальгаард (Peter Dalgaard) на пятой юбилейной конференции в 2020 году прямо говорил, что сложно успевать за изменениями и работать в прежнем высоком ритме. В конце концов, когда R Core Team формировалась, многим было уже около 40 лет. В результате изменения в базовом R редки и не всегда значительны, а общая стратегия развития языка и экосистемы не очень прослеживается и больше лежит в новых пакетах, чем в основах языка.

Ещё позже, в 2015 году, был образован R Consortium — объединение, призванное продвигать язык и среду, поддерживать сообщество и его мероприятия, способствовать взаимодействию R Foundation с разработчиками и пользователями.

R в первую очередь развивается за счёт сообщества разработчиков и практиков. Тем не менее, есть примеры коммерческого использования (правда, разной степени удачности) R. Так, в 2007 году появилась компания Revolution Analytics, которая предоставляла услуги по анализу данных на основе своего интерпретатора (Revolution R) и набора пакетов, в которых немалое внимание уделялось быстродействию. В 2015 году компанию купила Microsoft, и поддержку Microsoft R Open (переименованный Revolution R) внедрили в ряд продуктов Microsoft — Azure, Power BI, SQL Server и т. д. Впрочем, не сказать, что R стал одним из неотъемлемых элементов этих продуктов. В какое-то время компания Oracle проявила интерес к R — в 2012 году появилась Oracle R Enterprise — одна из компонент аналитической системы, идущей в пакете с базами данных Oracle. Из-за интеграции с базой данных Oracle R Enterprise позволяет работать напрямую с данными, хранящимися в таблице, притом делать не только манипуляции данными, но и статистические вычисления. Помимо Oracle и Microsoft есть и другие примеры интеграции R с другими продуктами, в основном это базы данных или ядра статистических программ (для некоторых областей важно, чтобы приложения были валидированы и соответствовали определенным стандартам, что в условиях полной open source разработки сложно достичь).

С момента появления R прошёл достаточно длинный путь: от десятка пакетов на старте до нескольких тысяч новых пакетов в год и миллионов загрузок старых пакетов. Также была проделана большая работа по увеличению быстродействия (по некоторым замерам, в несколько десятков раз), появились инструменты параллелизации и асинхронного выполнения выражений. Помимо разнообразия статистических пакетов и инструментов визуализации, в настоящий момент на R можно писать собственные веб-приложения (Shiny), вести блоги и сайты-визитки, создавать HTTP API, писать пайплайны ETL и процессинга данных, писать и готовить к публикации академические статьи, а также многое другое.

Экосистема и сообщество R

Так как R — это open source проект, то важную роль в его развитии играет сообщество. Силами сообщества пишутся и улучшаются пакеты, активисты организуют локальные группы пользователей (так называемые R user group), проводят митапы и конференции. В 2012 году в целях поддержки женщин-программистов и в целом повышения их видимости появилось движение R-Ladies, начало положила Габриэла де Кейроз (Gabriela de Queiroz). Она собрала в Сан-Франциско первую группу, в которой женщины могли бы свободно задавать вопросы и вносить предложения, не опасаясь критики и обесцениваний. На данный момент движение R-Ladies насчитывает более несколько десятков тысяч членов и около полутора сотен локальных отделений.

Помимо множества индивидуальных или малых групп разработчиков, в R-сообществе есть и более крупные команды, пакеты которых сформировали свои собственные экосистемы и микросообщества. Основные игроки на этом поле — проект Bioconductor и Хэдли Викхэм (Hadley Wickham, tidyverse). В какой-то мере к ним можно отнести и Мэтта Доула (Matt Dowle), создателя пакета data.table. Также есть и просто группы разработчиков, создавшие незаменимые для современного пользователя или разработчика инструменты. К подобным можно отнести Дирка Эдельбюттеля (Dirk Eddelbuettel) и его соавторов, написавших систему пакетов для работы с C++, группу ROpenSci, продвигающую идеи открытой науки и создавшую такие пакеты, как magick, plotly, RSelenium, drake (всего более 300 пакетов), и группу mlr, представившую систему пакетов для организации процесса машинного обучения (пайплайны, оптимизация, подбор параметров).

Bioconductor

Bioconductor — набор основанных на R инструментов для работы с геномными данными. Проект появился в 2001 году, к его появлению также приложил руку Роберт Джентельмен. На данный момент Bioconductor включает порядка двух тысяч пакетов, которые можно разделить на нескольких больших групп: пакеты-приложения, базы данных аннотаций геномов, базы данных экспериментов, некоторые инструменты для организации работы.

Пакеты Bioconductor обладают рядом особенностей. Во-первых, для большинства пакетов используется особая лицензия Artistic License, так как проект предполагает как свободно используемое программное обеспечение (free software), так и программы с открытым кодом (open source). Во-вторых, пакеты размещаются не в CRAN, а в собственном репозитории, и для установки используются собственные функции, а не классическая функция install.packages(). Третья специфика — немалая часть пакетов использует S4 для организации функций и классов.

В целом сообщество Bioconductor достаточно закрытое и централизованное, например, для разработчиков есть гайды и рекомендации по коду и оформлению документации, Bioconductor может нанимать разработчиков и аналитиков. Пользователи (да и разработчики) пакетов R, если они не занимаются биоинформатикой, весьма маловероятно столкнутся с необходимостью что-то устанавливать из пакетов Bioconductor. Впрочем, личный опыт показывает, что функционал некоторых пакетов Bioconductor в какой-то момент может появиться в пакетах в CRAN.

RStudio и tidyverse

Tidyverse — группа пакетов, объединённых общей идеологией модели данных, стилистикой кода и особенностями использований NSE (нестандартного выполнения выражений). Иногда tidyverse иронично называют hadleyverse — по имени ведущего разработчика и идеолога всей системы пакетов, Хэдли Викхема. Среди tidyverse-пакетов можно выделить базовые пакеты, которые и формируют всю специфику работы с данными, принятую в этой парадигме (dplyr, rlang, tidyr и т. д.), а также тематические пакеты, которые реализуют те или иные потребности пользователей (devtools, knitr, ggplot2, rvest и многие другие).

В базе концепции tidyverse лежит продолжение идеи, что пользователь R (S, так как корни там) — это специалист в области анализа данных и совсем не обязательно программист. Что в свою очередь накладывает требования простоты и интуитивной понятности кода в области манипуляции с данными или применения статистических методов вкупе с упрощением или сокрытием более низкоуровневых особенностей языка. Как постулируется в манифесте tidy-инструментов, во время анализа большую часть времени занимает продумывание и написание кода, а не собственно машинное время его выполнения. Основные принципы согласно манифесту:

  • Использование и переиспользование уже существующих структур данных. Этот принцип предполагает, что во всех пакетах экосистемы следует использовать данные одной структуры, чтобы не было ситуации, когда для каждого пакета своя структура. В tidyverse используется прямоугольный формат таблиц, в которых по строкам находятся наблюдения, а в столбцах — признаки. Этот формат знаком всем, кто занимается анализом данных экспериментов или машинным обучением. На более низком уровне используются базовые типы R или же S3-классы, опирающиеся на векторы или списки.

  • Сочетание простых функций в конвейере. Здесь есть сразу несколько важных следствий. Во-первых, используется unix-парадигма, когда одна функция хорошо выполняет одну задачу и нет многофункциональных комбайнов. Во-вторых, евангелистами tidyverse декларируется, что основа манипуляций с таблицами — пять ключевых функций (глаголов в терминологии tidyverse, так как рекомендуется называть функции глаголами). Эти пять функций: mutate(), select(), filter(), arrange() и summarise(), плюс группировка group_by(), а бэкендом могут быть как таблицы в базах данных, так и data.table-таблицы. Третье следствие, по которому в R-скриптах почти однозначно можно определить код в tidyverse-парадигме — это активное использование пайпов вида %>%. Как правило, этот код представляет собой ряд последовательных операций над таблицей, где каждая операция — один из глаголов, а все операции объединены в конвейер. Внутри каждой операции могут использоваться свои однозадачные функции. В примере ниже берётся датасет starwars, первой операцией отфильтровываются все строки, в которых в столбце species встречается значение Droid. Результат передается в функцию select(), с её помощью отбираются все столбцы, название которых оканчивается на color (функция ends_with()). Результат операции передаётся дальше, в операцию сортировки, и указывается, что сортировка должна идти по значениям столбца eye_color по убыванию (функция desc()).

library(dplyr)
starwars %>% 
  filter(species == 'Droid') %>%
  select(name, ends_with('color')) %>%
  arrange(desc(eye_color))
## # A tibble: 6 × 4
##   name   hair_color skin_color  eye_color
##   <chr>  <chr>      <chr>       <chr>    
## 1 C-3PO  <NA>       gold        yellow   
## 2 R4-P17 none       silver, red red, blue
## 3 R2-D2  <NA>       white, blue red      
## 4 R5-D4  <NA>       white, red  red      
## 5 IG-88  none       metal       red      
## 6 BB8    none       none        black
  • В манифесте декларируется следование идее функционального программирования. В частности, к нему относят концепцию функций-глаголов, которые что-то делают с таблицей. Предполагается, что такой стиль более привычен непрограммистам, в отличие от использования методов класса, характерного для более объектно-ориентированных языков. Помимо этого, tidyverse предполагает использование функций-дженериков и S3/S4-моделей, иммутабельность объектов и создание копии объекта при изменении (copy-on-modify).

  • Дизайн для людей. Этот принцип призывает не жалеть времени на продумывание названий функций и в целом заботиться об удобстве пользователя. В частности, предлагается делать относительно длинные и информативные названия функций, рассчитывать, как они будут обрабатываться автодополнениями IDE (делать общий префикс у близких по смыслу функций) и т. д.

Помимо этих принципов, в tidyvers расширяется и упрощается логика выполнения выражений, подход называется tidyeval. Например, названия столбцов указываются без кавычек, пайпы скрывают аргумент датасета из функций и оставляют только значимые аргументы, есть специальные функции, которые позволяют хранить названия столбцов в переменных и потом использовать их в третьих функциях и т. д. При внимательном рассмотрении немалая часть функций является “синтаксическим сахаром” над функциями базового R: например, ту же функцию ends_with() вполне можно заменить регулярным выражением.

С этим связано одно из направлений критики tidyverse: большое количество “сахара” в какой-то момент скрывает от пользователя особенности языка и вынуждает понимать не принципы работы тех или иных выражений, а учить достаточно большое количество мелких функций из множества пакетов tidyverse. При этом пакетов, которые разделяют парадигму tidyverse, несколько десятков и помнить, в каком из пакетов реализована необходимая функция, в какой-то момент становится сложно. Одним из следствий большого количества пакетов и мелких функций является сложность в изучении языка: несмотря на декларируемые пять глаголов, в реальности пользователи сталкиваются с намного большим количеством функций (в том же пакете dplyr их несколько сотен). Это парадокс: концепция, призванная упростить работу с языком, в какой-то момент начинает её усложнять.

Стремление к чрезмерному упрощению также приводит в некоторых случаях к плохо контролируемым ошибкам и сложностям. Разработчики стараются снизить когнитивную нагрузку на пользователя и стремятся сделать свои пакеты и функции максимально “умными”, а это не всегда полезно. Например, в пакете readxl версии 1.3.1 функции ориентируются на расширение файла и в зависимости от этого вызываются read_xls_() или read_xlsx_(). Вместо того чтобы прочитать первые несколько бит файла, которые вполне однозначно разделяют форматы. При этом никак не учитывается ситуация, когда файл *.xlsx сохранён как *.xls, что вполне можно встретить на практике. В результате приходится самостоятельно анализировать файл и переименовывать, чтобы ETL-скрипты его корректно обработали.

Активное развитие всей экосистемы нередко сопровождается достаточно радикальными переработками функционала. В результате нередко возникают проблемы с обратной совместимость, и работавший вчера код выдаёт множество не всегда очевидных ошибок. Это допустимо для тех, кто работает с данными ситуативно и/или интерактивно. Однако это отвращает от tidyverse тех, кто пишет на R системы сбора и анализа данных, работающие в автоматическом режиме, или пишет пакеты с использованием tidyverse-пакетов. Не говоря уже о том, что практика релизов без обратной совместимости в целом вызывает мало восторгов в среде разработчиков.

Другое и намного более серьёзное направление критики связано с тем, что немалая часть функций или стандартных названий базовых пакетов в tidyverse имеет свои собственные аналоги. В сочетании с достаточно расширенной и доработанной концепцией NSE это приводит к ситуации, когда человек, не использующий tidyverse, практически не может прочитать или отладить код, написанный tidyverse-энтузиастом, и наоборот. В какой-то мере можно сказать, что tidyverse — это фактически новый диалект R и мы имеем достаточно явный раскол сообщества на тех, кто использует tidyverse для работы с данными, и тех, кто использует другие инструменты. Из личного опыта могу сказать, что, на мой взгляд, сочетания base R + data.table достаточно для подавляющего числа задач манипуляций с данными.

tidyverse поддерживает компания RStudio, разработчик одноименной IDE. Хэдли Викхэм — ведущий сотрудник компании, многие авторы и соавторы tideverse-пакетов также работают в RStudio. Это достаточно двойственная ситуация: с одной стороны, благодаря поддержке успешной бизнес-компании появляются отличные высококачественные пакеты и в целом идёт развитие. С другой стороны, возникает определённая диспропорция — Мэтт Доул как-то отметил, что когда евангелисты tidyverse говорят о манипуляции с данными в R, они крайне редко упоминают другие подходы, например data.table, в то время как этот пакет по бенчмаркам превосходит все близкие аналоги (и tibble, и pandas в Python) и намного эффективнее при работе с большими данными и в целом лаконичнее. В результате раскол между сторонниками tidyverse и всеми прочими лишь только усиливается. В сочетании с тем, что R Core Team/R Foundation не сказать чтобы активно вовлечены в проблему, перспективы дальнейшего развития сообщества складываются весьма неоднозначные. С одной стороны, хорошо, что язык достаточно гибок и даёт возможность развития подобных диалектов, разнообразие решений и подходов можно только приветствовать; с другой же стороны, дробление на плохо понимающие друг друга группы людей выглядит несколько сомнительно с точки зрения эффективности обмена опытом, обучения и поддержки друг друга членами сообщества.