Открыть сервис

Either в языках программирования

Either — в программировании, в частности в функциональных языках и языках со статической типизацией, — это параметризованный тип данных (конструктор типа), который представляет значение одного из двух возможных типов: левого (обычно обозначается Left) или правого (Right). Концепция восходит к теории типов и математическому понятию дизъюнкции (суммы типов). В отличие от логического ИЛИ (OR), тип Either не является «ленивым» или «короткозамкнутым» в том смысле, что он всегда содержит ровно одно значение, но это значение принадлежит строго одному из двух альтернативных типов.

Происхождение и теоретическая основа

Тип Either является классическим примером алгебраического типа данных, а именно — суммой (копроизведением) двух типов. В теории категорий это соответствует бинарному копроизведению в категории множеств. Если типы A и B — это множества, то Either A B — это размеченное объединение этих множеств. Конструкторы Left (для значения типа A) и Right (для значения типа B) служат инъекциями, сохраняющими информацию о том, из какого исходного множества взято значение. Это отличает Either от обычного объединения типов (например, union в C), которое не сохраняет метку происхождения.

В языках программирования, основанных на системе типов Хиндли — Милнера (таких как Haskell, ML), Either определяется как: ``haskell data Either a b = Left a | Right b ` Здесь a и b — параметры типа (переменные типа), а Left и Right` — конструкторы данных.

Использование для обработки ошибок

Наиболее распространённое применение Either в индустриальном программировании — это альтернатива механизму исключений (exceptions) для обработки ошибок. В этом случае по соглашению тип Left используется для хранения информации об ошибке, а тип Right — для успешного результата вычисления. Такое соглашение принято в языках Scala, Kotlin (в виде библиотечного Either), а также в Haskell (хотя там чаще используется специализированный синоним Except).

Преимущества подхода:

  • Чистота функций: функция, возвращающая Either, является тотальной (всегда возвращает значение, а не «взрывается» исключением), что упрощает рассуждения о коде и тестирование.
  • Явность: сигнатура функции явно указывает, что операция может завершиться неудачей, и какой тип ошибки возможен.
  • Композиция: наличие монадического интерфейса позволяет последовательно соединять вычисления, которые могут завершиться ошибкой, без глубокой вложенности условных операторов.

Пример на Scala: ``scala def parseNumber(s: String): Either[String, Int] = try Right(s.toInt) catch { case _: NumberFormatException => Left(s"Некорректное число: $s") } ``

Отличие от Option и Result

Either часто путают с типом Option (в Rust — Option, в Java — Optional). Ключевое отличие: Option имеет только один «провальный» вариант — None, который не несёт никакой информации. Either позволяет параметризовать вариант ошибки, передавая в Left произвольное значение (текст сообщения, код ошибки, объект исключения). Таким образом, Option можно представить как частный случай Either с типом ошибки Unit (пустой тип).

В языках, где Either не входит в стандартную библиотеку (например, в C++, Java), часто создают специализированные типы, такие как Result<T, E> (в Rust, Swift). По сути, Result — это тот же Either, но с фиксированными именами конструкторов (Ok/Err в Rust, Success/Failure в Swift) и более строгой семантикой: тип ошибки обычно ограничен типажом Error. В Rust Result является основным механизмом обработки ошибок, а Option используется для указания на возможное отсутствие значения (например, поиск по словарю).

Операции и методы

В зависимости от языка, Either поддерживает набор функций высшего порядка:

  • map (или mapRight): применяет функцию к значению внутри Right, оставляя Left без изменений.
  • mapLeft: применяет функцию к значению внутри Left.
  • flatMap (или bind): позволяет последовательно выполнять вычисления, возвращающие Either. Если первое вычисление вернуло Left, последующие не выполняются (аналог короткого замыкания).
  • fold (или match): сопоставление с образцом, позволяющее извлечь значение, обработав оба случая. Например, в Scala: either.fold(err => ..., success => ...).
  • swap: меняет местами Left и Right (полезно, если ошибка становится результатом).
  • isLeft / isRight: предикаты проверки варианта.

В языках с поддержкой сопоставления с образцом (Haskell, Scala, Rust) деструктуризация Either является идиоматическим способом ветвления логики.

Either в конкретных языках

Haskell

В Haskell Either определён в стандартной библиотеке Prelude. Начиная с версии GHC 7.10, Either является монадой (ошибка хранится в Left). Это позволяет использовать do-нотацию для последовательности операций.

Scala

В стандартной библиотеке Scala (до версии 2.12) Either был не право-ассоциативным (не был монадой по умолчанию), что вызывало критику. Начиная с Scala 2.12, Either стал право-биасным (right-biased), что означает, что операции map и flatMap работают с Right, как с ожидаемым результатом. В Scala 3 (Dotty) Either остаётся в стандартной библиотеке, но для обработки ошибок часто используется библиотека cats с типом EitherT для стекобезопасной композиции в монадных трансформерах.

Kotlin

В стандартной библиотеке Kotlin нет типа Either, однако он присутствует в популярных библиотеках функционального программирования, таких как Arrow. В Arrow тип Either<L, R> является право-биасным и предоставляет обширный набор операторов.

TypeScript / JavaScript

В TypeScript Either можно реализовать с помощью discriminated unions: ``typescript type Either<L, R> = { type: 'left'; value: L } | { type: 'right'; value: R }; ` Однако в сообществе чаще используется библиотека fp-ts, где Either` реализован в функциональном стиле с поддержкой монадических операций.

Java

В Java Either отсутствует в стандартной библиотеке. Для этих целей используются библиотеки типа vavr (ранее Javaslang), которая предоставляет Either с методами map, flatMap и т.д. Альтернативно, начиная с Java 8, для обработки ошибок часто применяется Optional (не параметризуемый ошибкой) или собственная реализация Result.

Критика и ограничения

Основная критика использования Either для обработки ошибок связана с тем, что он не различает разные типы ошибок на уровне системы типов (если не использовать вложенные Either). Это приводит к тому, что функция, ожидающая конкретную ошибку, вынуждена обрабатывать все возможные ошибки из нижележащего слоя. Для решения этой проблемы в языках с поддержкой расширяемых эффектов (например, в Haskell с использованием mtl или в Scala с cats-effect) применяются более сложные механизмы, такие как классы типов MonadError или Extensible Effects.

Другое ограничение — отсутствие стековой трассировки в случае ошибки, если в Left кладётся просто строка. Поэтому рекомендуется в Left помещать объекты, содержащие контекст ошибки, либо использовать специализированные типы (Error, Throwable), чтобы сохранить информацию для диагностики.

BFOmetr — база данных и аналитика по компаниям России.

На главную BFOmetr →