Either: istota, zastosowanie i zasada działania

Autor: IT Sectr Opublikowano: 2026-05-26 Czas czytania: 8 min

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 — algebraiczny typ danych do reprezentowania dwóch wzajemnie wykluczających się wariantów: Left (błąd) i Right (sukces).
  • Left tradycyjnie zawiera informację o błędzie, Right — poprawną wartość.
  • Jawna typizacja — Either wymusza obsługę obu wariantów na etapie kompilacji, eliminując nieoczekiwane awarie.
  • Kompozycja — Either obsługuje map, flatMap i fold do łańcuchów przekształceń bez zagnieżdżonych sprawdzeń.
  • Różnica od Result — Either może przechowywać dowolny typ błędu, a nie tylko Error lub Throwable.

Czym jest Either?

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.

Jak działa Either: struktura Left i Right

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.

Typ Left: kontener błędu

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.

Typ Right: kontener sukcesu

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.

Kompozycja Either przez flatMap

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.

kotlin
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) }

Either w Kotlin: praktyczne zastosowanie

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.

Biblioteka Arrow i Either

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().

Przykład: Either z zapytaniem sieciowym

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.

kotlin
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 ?: ""))
    }
}

Either w Swift: różnica od standardowego Result

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.

Standardowy Result kontra Either

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.

swift
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 }

Kiedy używać Either w aplikacjach mobilnych

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.

  • Zapytania sieciowe — każde zapytanie może zwrócić błąd połączenia, serwera lub parsowania. Either zbiera wszystkie warianty w jeden typ.
  • Walidacja formularzy — Either z niestandardowym typem błędu wygodnie reprezentuje wynik sprawdzania pól wejściowych: nieprawidłowy email lub hasło.
  • Repozytorium danych — Either pozwala połączyć wynik z różnych źródeł (cache, baza danych, sieć) z jednolitym typem błędu.
  • Interpreter poleceń — jeśli aplikacja analizuje dane wejściowe użytkownika lub polecenia, Either zapewnia typowo bezpieczny sposób zgłoszenia błędu.

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.

Typowe błędy przy pracy z Either

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.

  • Ignorowanie Left — wywołanie .getOrElse() z wartością domyślną bez analizy przyczyny błędu pozbawia sensu użycie Either. Obsługuj błąd jawnie.
  • Globalny typ błędu — używanie String lub Exception jako wspólnego typu dla wszystkich Either w projekcie obniża bezpieczeństwo typów. Twórz domenowe sealed class.
  • Zagnieżdżone Either — powtórny Either wewnątrz Right (Either<E, Either<E, A>>) utrudnia czytanie. Używaj flatMap do spłaszczania.
  • Mieszanie z wyjątkami — funkcja zwraca Either, ale wewnętrznie rzuca wyjątek. To przeczy idei jawnej obsługi błędów.

Często zadawane pytania

Czym Either różni się od Optional?

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.

Czy można używać Either w Java?

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.

Kiedy lepiej użyć sealed class zamiast Either?

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.

Czy Either obsługuje wielowątkowość?

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.

Czy warto używać Either dla wszystkich funkcji w projekcie?

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

  • Either — typ funkcyjny do reprezentowania dwóch wariantów: Left (błąd) i Right (sukces), czyniący obsługę awarii jawną na poziomie typów.
  • Left może zawierać dowolny typ błędu — od String do domenowego sealed class, w przeciwieństwie do standardowego Result.
  • flatMap zapewnia kompozycję Either bez zagnieżdżonych sprawdzeń: błąd automatycznie propaguje się przez cały łańcuch.
  • Arrow — główna biblioteka Either dla Kotlin z wbudowanym wsparciem dla korutyn i rozszerzeń.
  • Swift używa wbudowanego Result, ale Either implementuje się przez enum z dwoma parametrami generycznymi dla pełnej elastyczności.
  • Stosuj Either dla zapytań sieciowych, walidacji formularzy i repozytoriów — scenariuszy z oczekiwanymi błędami.
  • Unikaj globalnego typu błędu, ignorowania Left i zagnieżdżonych Either — to zmniejsza zalety bezpieczeństwa typów.

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.

Omów projekt

Przeczytaj również