Either — typ funkcyjny, który reprezentuje wartość jednego z dwóch możliwych wariantów: lewy (Left) dla błędu i prawy (Right) dla sukcesu. W przeciwieństwie do wyjątków, Either czyni obsługę błędów jawną na poziomie typów i nie wymaga bloków try-catch. Według danych Arrow, 2026, Either jest szeroko stosowany w projektach Kotlin do kompozycji operacji, które mogą zakończyć się niepowodzeniem, bez efektów ubocznych.
Najważniejsze
Either — to algebraiczny typ danych zapożyczony z programowania funkcyjnego, który reprezentuje wartość dokładnie jednego z dwóch możliwych typów. W kontekście obsługi błędów przyjęto konwencję: lewy typ (Left) zawiera opis problemu, a prawy (Right) — wynik powodzenia.
Koncepcja Either wywodzi się z języka Haskell i kategorii typów Either a b, gdzie a to typ wartości lewej, b — prawej. W tworzeniu aplikacji mobilnych Either zyskał popularność dzięki bibliotece Arrow dla Kotlin i funkcyjnym podejściom w Swift.
Kluczową zaletą Either nad wyjątkami jest brak ukrytych ścieżek wykonania. Funkcja zwracająca Either jawnie deklaruje w swojej sygnaturze, że może zakończyć się błędem. Kompilator kontroluje, czy oba warianty zostały obsłużone.
W przeciwieństwie do rzucania wyjątków, Either zachowuje przezroczystość przepływu danych. Wywołanie funkcji zwracającej Either nie wymaga try-catch po stronie kodu wywołującego — wystarczy pattern-matching lub fold. Jest to szczególnie ważne w architekturach z podejściem reaktywnym, gdzie każde źródło danych zwraca Either, a łańcuch transformacji buduje się przez map i flatMap.
Either składa się z dwóch podtypów: Left i Right. Instancja Either może być tylko jednym z nich w danym momencie. Typowa sygnatura w Kotlin wygląda jako Either<E, A>, gdzie E to typ błędu, A — typ wartości powodzenia.
Left reprezentuje przypadek awarii. W przeciwieństwie do wyjątków, Left nie przerywa przepływu wykonania — po prostu zwraca wartość, którą należy obsłużyć. Typ błędu może być dowolny: String, Int, niestandardowy sealed class lub model domenowy.
Right zawiera poprawny wynik operacji. Nazwa odzwierciedla „poprawność" — zgodnie z konwencją Right oznacza sukces. To na Right stosuje się transformacje map i flatMap, co pozwala budować łańcuchy obliczeń bez sprawdzania każdego kroku.
flatMap — główny mechanizm kompozycji Either. Jeśli bieżąca wartość to Right, flatMap stosuje przekazaną funkcję i zwraca nowy Either. Jeśli Left — flatMap pomija przekształcenie i przekazuje błąd dalej. To zachowanie nazywa się short-circuit evaluation.
Oprócz flatMap, Either obsługuje mapLeft do transformacji błędu, fold do obsługi obu wariantów w jednym miejscu oraz getOrElse do wyodrębnienia wartości z domyślną. Te funkcje pokrywają wszystkie scenariusze: od prostego wyodrębnienia do złożonej kompozycji z wywołaniami asynchronicznymi w korutynach Kotlin lub Combine w Swift.
fun parseInt(input: String): Either<String, Int> =
input.toIntOrNull()?.let { Right(it) }
?: Left("Nie można przekonwertować: $input")
fun divide(a: Int, b: Int): Either<String, Int> =
if (b == 0) Left("Dzielenie przez zero")
else Right(a / b)
val result = parseInt("10")
.flatMap { divide(it, 2) }
W ekosystemie Kotlin Either jest zaimplementowany w bibliotece Arrow. Standardowa biblioteka Kotlin oferuje Result, ale Either daje więcej elastyczności: dowolny typ błędu, kompozycję przez flatMap i wsparcie dla wzorców funkcyjnych.
Arrow — biblioteka funkcyjna dla Kotlin, która dodaje Either, Option, Validated i inne typy. Arrow.Either — sealed class z dwoma dziedzicami: ArrowCore.Left i ArrowCore.Right. Biblioteka dostarcza również rozszerzenia do wygodnej pracy: .getOrElse(), .fold(), .mapLeft().
Rozważmy rzeczywisty scenariusz — zapytanie sieciowe w aplikacji Android z możliwymi błędami: brak sieci, błąd serwera, nieprawidłowa odpowiedź. Either pozwala połączyć wszystkie warianty w jednym typie zwracanym.
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 ?: ""))
}
}
W Swift od wersji 5.0 pojawił się wbudowany typ Result, który koncepcyjnie przypomina Either, ale ma ograniczenia: błąd musi odpowiadać protokołowi Error, a wartość powodzenia — jednemu typowi. Either w Swift implementuje się przez enum z dwoma parametrami generycznymi.
Result<Success, Failure> — wbudowany typ Swift, gdzie Failure: Error. Either nie nakłada ograniczeń na typ błędu, co pozwala przechowywać String, niestandardowe struktury, a nawet kilka typów błędów przez zagnieżdżone 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 jest optymalny w scenariuszach wymagających jawnej i typowo bezpiecznej obsługi błędów bez wyjątków. Rozważmy główne przypadki zastosowania w tworzeniu aplikacji mobilnych w Kotlin i Swift.
Nie należy używać Either do prostych operacji bez efektów ubocznych — zwykła zwracana wartość jest bardziej niezawodna i czytelniejsza. Either jest również zbędny, gdy awaria to sytuacja wyjątkowa, a nie oczekiwany scenariusz.
Either jest również skuteczny przy pracy z korutynami w Kotlin. Funkcja zwracająca Either może być wywołana wewnątrz korutyny z obsługą błędu przez fold lub mapLeft bez blokowania wątku. Jest to szczególnie przydatne w aplikacjach Android z architekturą MVVM, gdzie każde repozytorium zwraca Either, a ViewModel transformuje wynik do UiState.
Programiści po raz pierwszy zapoznający się z Either często popełniają podobne błędy. Rozważmy najczęstsze z nich i sposoby ich unikania.
Często zadawane pytania
Optional reprezentuje obecność lub brak wartości (Some/None), ale nie podaje przyczyny braku. Either daje dwa konkretne typy — lewy dla błędu i prawy dla sukcesu, co pozwala przekazać kontekst awarii.
W Java nie ma wbudowanego Either, ale biblioteki Vavr i functionaljava dostarczają implementację. W tworzeniu aplikacji Android Either z Vavr to popularna alternatywa dla stylu funkcyjnego z lambdami.
Sealed class w Kotlin jest wygodniejszy, gdy wariantów jest więcej niż dwa lub mają różną strukturę. Dla binarnego wyniku (błąd/sukces) Either jest bardziej zwięzły i dostarcza gotowe kombiratory funkcyjne.
Either jest niemutowalny i bezpieczny wątkowo domyślnie. W Kotlin z korutynami Either doskonale się łączy: flatMap działa wewnątrz coroutine scope, a obsługa błędów nie wymaga blokad.
Nie. Either jest odpowiedni dla operacji z oczekiwanymi awariami (sieć, walidacja, logika biznesowa). Dla prostych getterów i obliczeń bez efektów ubocznych zwykły typ jest czytelniejszy i nie dodaje zbędnej złożoności.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również