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 →

