Either — функциональный тип, который представляет значение одного из двух возможных вариантов: левый (Left) для ошибки и правый (Right) для успеха. В отличие от исключений, Either делает обработку ошибок явной на уровне типов и не требует блоков try-catch. По данным Arrow, 2026, Either широко применяется в Kotlin-проектах для композиции операций, которые могут завершиться сбоем, без побочных эффектов.
Главное
Either — это алгебраический тип данных, заимствованный из функционального программирования, который представляет значение ровно одного из двух возможных типов. В контексте обработки ошибок принято соглашение: левый тип (Left) содержит описание проблемы, а правый (Right) — успешный результат.
Концепция Either восходит к языку Haskell и категории типов Either a b, где a — тип левого значения, b — правого. В мобильной разработке Either получил распространение благодаря библиотеке Arrow для Kotlin и функциональным подходам в Swift.
Ключевое преимущество Either перед исключениями — отсутствие скрытых путей выполнения. Функция, возвращающая Either, явно заявляет в своей сигнатуре, что может завершиться ошибкой. Компилятор контролирует, обработаны ли оба варианта.
В отличие от выбрасывания исключений, Either сохраняет прозрачность потока данных. Вызов функции, возвращающей Either, не требует try-catch на стороне вызывающего кода — достаточно pattern-matching или fold. Это особенно важно в архитектурах с реактивным подходом, где каждый источник данных возвращает Either, а цепочка трансформаций строится через map и flatMap.
Either состоит из двух подтипов: Left и Right. Экземпляр Either может быть только одним из них в каждый момент времени. Типовая сигнатура в Kotlin выглядит как Either<E, A>, где E — тип ошибки, A — тип успешного значения.
Left представляет случай сбоя. В отличие от исключений, Left не прерывает поток выполнения — он просто возвращает значение, которое нужно обработать. Тип ошибки может быть любым: String, Int, кастомный sealed class или доменная модель.
Right содержит корректный результат операции. Название отражает «правильность» — по соглашению Right означает успех. Именно на Right применяются трансформации map и flatMap, что позволяет строить цепочки вычислений без проверки каждого шага.
flatMap — основной механизм композиции Either. Если текущее значение — Right, flatMap применяет переданную функцию и возвращает новый Either. Если Left — flatMap пропускает преобразование и передаёт ошибку дальше. Это поведение называется short-circuit evaluation.
Помимо flatMap, Either поддерживает mapLeft для трансформации ошибки, fold для обработки обоих вариантов в одном месте и getOrElse для извлечения значения с дефолтом. Эти функции покрывают все сценарии: от простого извлечения до сложной композиции с асинхронными вызовами в корутинах Kotlin или Combine в Swift.
fun parseInt(input: String): Either<String, Int> =
input.toIntOrNull()?.let { Right(it) }
?: Left("Не удалось преобразовать: $input")
fun divide(a: Int, b: Int): Either<String, Int> =
if (b == 0) Left("Деление на ноль")
else Right(a / b)
val result = parseInt("10")
.flatMap { divide(it, 2) }
В экосистеме Kotlin Either реализован в библиотеке Arrow. Стандартная библиотека Kotlin предлагает Result, но Either даёт больше гибкости: произвольный тип ошибки, композиция через flatMap и поддержка функциональных паттернов.
Arrow — функциональная библиотека для Kotlin, которая добавляет Either, Option, Validated и другие типы. Arrow.Either — sealed class с двумя наследниками: ArrowCore.Left и ArrowCore.Right. Библиотека также предоставляет расширения для удобной работы: .getOrElse(), .fold(), .mapLeft().
Рассмотрим реальный сценарий — сетевой запрос в Android приложении с возможными ошибками: отсутствие сети, ошибка сервера, некорректный ответ. Either позволяет объединить все варианты в одном возвращаемом типе.
sealed class NetworkError {
data class NoConnection(val message: String): NetworkError()
data class ServerError(val code: Int): NetworkError()
data class ParseError(val detail: String): NetworkError()
}
suspend fun fetchUser(id: String): Either<NetworkError, User> {
return try {
val response = api.getUser(id)
if (response.isSuccessful) {
Right(response.body()!!)
} else {
Left(NetworkError.ServerError(response.code()))
}
} catch (e: IOException) {
Left(NetworkError.NoConnection(e.message ?: ""))
}
}
В Swift начиная с версии 5.0 появился встроенный Result тип, который концептуально похож на Either, но имеет ограничения: ошибка должна соответствовать протоколу Error, а успешное значение — одному типу. Either в Swift реализуется через enum с двумя generic-параметрами.
Result<Success, Failure> — встроенный тип Swift, где Failure: Error. Either не накладывает ограничений на тип ошибки, что позволяет хранить String, кастомные структуры или даже несколько типов ошибок через вложенные enum.
enum Either<E, A> {
case left(E)
case right(A)
func map<B>(_ transform: (A) -> B) -> Either<E, B> {
switch self {
case .left(let e): return .left(e)
case .right(let a): return .right(transform(a))
}
}
}
let result: Either<String, Int> = .right(42)
let mapped = result.map { $0 * 2 }
Either оптимален для сценариев, где требуется явная и типобезопасная обработка ошибок без исключений. Рассмотрим основные случаи применения в мобильной разработке на Kotlin и Swift.
Не стоит использовать Either для простых операций без побочных эффектов — обычное возвращаемое значение надёжнее и читается легче. Также Either избыточен, когда сбой — это исключительная ситуация, а не ожидаемый сценарий.
Either также эффективен при работе с корутинами в Kotlin. Функция, возвращающая Either, может быть вызвана внутри корутины с обработкой ошибки через fold или mapLeft без блокировки потока. Это особенно полезно в Android-приложениях с архитектурой MVVM, где каждый репозиторий возвращает Either, а ViewModel трансформирует результат в UiState.
Разработчики, впервые знакомящиеся с Either, часто допускают схожие ошибки. Рассмотрим наиболее распространённые и способы их избежать.
Часто задаваемые вопросы
Optional представляет наличие или отсутствие значения (Some/None), но не сообщает причину отсутствия. Either даёт два конкретных типа — левый для ошибки и правый для успеха, что позволяет передавать контекст сбоя.
В Java нет встроенного Either, но библиотеки Vavr и functionaljava предоставляют реализацию. В Android-разработке Either из Vavr — популярная альтернатива для функционального стиля с лямбдами.
Sealed class в Kotlin удобнее, когда вариантов больше двух или они имеют разную структуру. Для бинарного исхода (ошибка/успех) Either компактнее и предоставляет готовые функциональные комбинаторы.
Either иммутабелен и потокобезопасен по умолчанию. В Kotlin с корутинами Either отлично комбинируется: flatMap работает внутри coroutine scope, а обработка ошибок не требует блокировок.
Нет. Either уместен для операций с ожидаемыми сбоями (сеть, валидация, бизнес-логика). Для простых геттеров и вычислений без побочных эффектов обычный тип читается проще и не добавляет лишней сложности.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также