Функции и пакеты
Функции и пакеты
Функции — основная единица логики в R; пакеты группируют функции, данные и тесты для повторного использования. Если вы уже видели <-, векторы и library() в основах или первой программе, эта глава систематизирует объявление функций, аргументы ... и правила подключения пакетов без конфликтов имён. Обобщённо про вызов и стек — функции в коде.
Интерактивное демо — вызов функции и стек на примере JavaScript. В R объявление другое, но вызов, аргументы и возврат устроены так же.
Play ITЗагрузка интерактивного демо…
Что такое функция в R
Функция в R — это именованный или анонимный блок кода, который принимает входные данные (аргументы), выполняет последовательность операций и возвращает результат. Вызов функции инициирует выполнение её тела — набора выражений, заключённых между фигурными скобками. После завершения работы функция передаёт управление обратно вызывающему коду и возвращает значение последнего вычисленного выражения, если явный возврат не задан с помощью ключевого слова return().
Функции в R могут быть встроенными (предоставленными базовой системой или пакетами), пользовательскими (написанными самим разработчиком) или анонимными (созданными без присвоения имени, часто используемыми в контексте передачи как аргумент).
Структура функции
Объявление функции в R осуществляется с помощью ключевого слова function. Минимальная структура выглядит следующим образом:
имя_функции <- function(аргумент1, аргумент2, ...) {
# тело функции
}
Здесь:
имя_функции— идентификатор, по которому функцию можно вызывать.function(...)— специальная конструкция, создающая объект функции.- В круглых скобках перечисляются формальные аргументы — параметры, которые функция ожидает получить при вызове.
- Тело функции содержит последовательность выражений, которые будут выполнены при вызове.
Аргументы функции могут иметь значения по умолчанию. Это позволяет вызывать функцию, не указывая все аргументы, если их поведение заранее определено. Например:
приветствие <- function(имя = "Гость") {
paste("Привет,", имя)
}
Вызов приветствие() вернёт "Привет, Гость", а вызов приветствие("Анна") — "Привет, Анна".
Аргументы функций
Аргументы в R обладают рядом особенностей. Частичное сопоставление имён (partial matching) позволяет передать уникальный префикс вместо полного имени аргумента — удобно в интерактиве, но источник тихих багов в скриптах. В продакшене лучше указывать полные имена: mean(x, na.rm = TRUE).
Lazy evaluation (ленивые аргументы): выражение аргумента вычисляется только при первом обращении к нему в теле функции — экономит работу, если аргумент не использован.
В-третьих, функции могут принимать переменное число аргументов с помощью специального символа ... (многоточие). Этот механизм позволяет передавать произвольное количество дополнительных аргументов, которые затем могут быть использованы внутри функции или переданы дальше другим функциям. Многоточие особенно полезно при создании обёрток вокруг других функций или при проектировании интерфейсов высокого уровня.
Возврат значений
Каждая функция в R возвращает ровно одно значение. Это значение может быть любого типа — числом, строкой, вектором, списком, фреймом данных, графиком или даже другой функцией. Если в теле функции не указан явный вызов return(), R автоматически возвращает результат последнего вычисленного выражения.
Ключевое слово return() используется для немедленного завершения выполнения функции и возврата указанного значения. Оно особенно полезно в условных конструкциях, где необходимо прекратить выполнение функции до достижения её конца.
Стоит отметить, что в R невозможно вернуть "ничего" в буквальном смысле. Даже если функция ничего явно не возвращает, она всё равно возвращает объект типа NULL, который представляет собой пустое значение.
Область видимости и замыкания
R использует лексическую область видимости (lexical scoping). Это означает, что при поиске значения переменной R сначала просматривает локальное окружение функции, затем окружение, в котором функция была определена, и далее — родительские окружения вплоть до глобального. Такой механизм позволяет создавать замыкания — функции, которые "запоминают" своё окружение на момент создания.
Замыкания широко применяются в R для создания функций с внутренним состоянием, генераторов, конфигурируемых обработчиков и других продвинутых паттернов. Например, можно создать функцию, которая возвращает другую функцию, зависящую от параметров внешней функции:
умножитель <- function(коэффициент) {
function(x) x * коэффициент
}
удвоить <- умножитель(2)
утроить <- умножитель(3)
удвоить(5) # 10
утроить(5) # 15
Здесь умножитель — это фабрика функций, а удвоить и утроить — замыкания, каждое из которых сохраняет своё значение коэффициент.
Функции высшего порядка
R активно поддерживает функции высшего порядка — функции, которые принимают другие функции в качестве аргументов или возвращают их как результат. Такие функции лежат в основе функционального стиля программирования и позволяют писать компактный, выразительный и переиспользуемый код.
Классическими примерами функций высшего порядка в R являются:
lapply(),sapply(),vapply()— применяют функцию к каждому элементу списка или вектора.Map()— применяет функцию к соответствующим элементам нескольких списков или векторов.Reduce()— последовательно комбинирует элементы вектора с помощью бинарной функции.Filter()— выбирает элементы, удовлетворяющие условию, заданному предикатной функцией.Find()— находит первый элемент, удовлетворяющий условию.
Эти функции позволяют избегать явных циклов и делают код более декларативным. Например, вместо того чтобы писать цикл for для возведения каждого элемента вектора в квадрат, можно просто вызвать sapply(вектор, function(x) x^2).
Анонимные функции
Анонимные функции — это функции без имени, которые создаются непосредственно в месте использования. Они особенно удобны при передаче коротких, одноразовых функций в качестве аргументов другим функциям. В R анонимные функции записываются так же, как и обычные, но без присваивания имени:
sapply(1:5, function(x) x^2)
Начиная с версии R 4.1.0, язык также поддерживает сокращённый синтаксис для анонимных функций с помощью символа \:
sapply(1:5, \(x) x^2)
Такой стиль делает код ещё более лаконичным и читаемым, особенно при работе с простыми преобразованиями.
Встроенные функции и пакеты
R поставляется с обширной коллекцией встроенных функций, охватывающих широкий спектр задач — от базовых математических операций (sum, mean, sd) до сложных статистических методов (lm, glm, t.test). Кроме того, экосистема R включает тысячи пакетов, каждый из которых предоставляет дополнительные функции для специализированных областей — машинного обучения, визуализации данных, работы с временными рядами, биоинформатики и многих других.
Пользователи расширяют R пакетами с CRAN, Bioconductor или GitHub:
install.packages("ggplot2")
library(ggplot2)
# или без загрузки в search path —
ggplot2::ggplot(mtcars, ggplot2::aes(wt, mpg)) + ggplot2::geom_point()
library(pkg) подключает пакет; require(pkg) возвращает FALSE, если пакет недоступен (не останавливает скрипт). В коде пакетов предпочтительнее requireNamespace("pkg") и вызовы через pkg::fun().
Жизненный цикл пакета в проекте
| Шаг | Команда / действие | Зачем |
|---|---|---|
| Установка | install.packages("dplyr") | скачать с CRAN в библиотеку R |
| Подключение | library(dplyr) | добавить функции в search path |
| Явный вызов | dplyr::filter(...) | избежать конфликта с stats::filter |
| Справка | ?filter, help(package = "dplyr") | документация в той же сессии |
| Версии проекта | renv::init() | зафиксировать набор пакетов — архитектура |
Конфликты имён (например, filter, lag) R сообщает при library(). В скриптах анализа безопаснее префикс pkg:: или conflicted::conflict_prefer("filter", "dplyr").
Создание собственного пакета — отдельная дисциплина — структура каталогов R/, man/, tests/, сборка через devtools / usethis, публикация на CRAN или GitHub. Для внутренних библиотек компании достаточно локального репозитория; для учебного проекта часто хватает папки R/ с .R файлами и source().
# минимальная "обёртка" без полноценного пакета
source("R/helpers.R")
normalize_amount <- function(x) {
x[is.na(x)] <- 0
x
}
Интеграция с Python — пакет reticulate; с C++ для ускорения — Rcpp — подробнее в архитектуре.
Документирование функций
Хорошая практика в R — сопровождать каждую пользовательскую функцию документацией. Хотя R не требует обязательного документирования, наличие комментариев и описаний значительно упрощает поддержку кода и его совместное использование. Для профессиональной разработки пакетов используется система документации Roxygen2, которая позволяет писать документацию прямо над определением функции в виде специальных комментариев. Эти комментарии затем автоматически преобразуются в стандартные файлы справки в формате Rd.
Даже при написании скриптов вне пакетов рекомендуется указывать назначение функции, описание аргументов и примеры использования. Это делает код самодостаточным и понятным даже спустя длительное время.
Отладка и тестирование функций
Поскольку функции являются основными единицами логики в R, их корректность критически важна. R предоставляет встроенные средства отладки, такие как browser(), debug(), traceback() и recover(), которые позволяют пошагово выполнять код, исследовать переменные и анализировать стек вызовов.
Для автоматизированного тестирования функций широко используется пакет testthat, который реализует подход, основанный на ожиданиях (expectations). С его помощью можно писать тесты, проверяющие, что функция возвращает ожидаемый результат при заданных входных данных, корректно обрабатывает крайние случаи и выбрасывает ошибки при недопустимых аргументах.
# фрагмент testthat
test_that("normalize_amount заменяет NA на 0", {
expect_equal(normalize_amount(c(1, NA, 3)), c(1, 0, 3))
})
Тесты входят в обязательный набор проверок CRAN; в корпоративных пакетах их запускают в CI вместе с R CMD check.