Kihagyás

Vavr Functional

Mi ez és mikor kell

  • A JDK-ból hiányzó funkcionális építőkövek: Option, Try, Either, Validation, persistent collections, Tuple, Lazy.
  • Mind a Value leszármazottja, ezért közös API: getOrElse, isEmpty, toList, toOption, toJavaOptional.
  • Ma a JDK sokat kivált: sealed + record + switch pattern matching = Either/Match library nélkül.
  • Vavr marad értelmes: Validation, Try, persistent collections.

Option

Option.of(null);        // None
Option.some(null);      // Some(null)  <- csapda
Option.of(5).map(x -> (Integer) null);  // Some(null)! Optional.map itt empty lenne
Option.of(5).flatMap(x -> Option.of(foo(x)));  // ez a null-biztos változat
  • Serializable, Iterable, gazdagabb API, mint az Optional.

Try

Try.of(() -> client.fetch(id))                 // checked exception is mehet
   .recover(IOException.class, e -> cached(id))
   .onFailure(e -> log.warn("fetch failed", e))
   .getOrElse(DEFAULT);
// Success(value) | Failure(ex)
  • Csak non-fatal exceptiont fog el. InterruptedException, VirtualMachineError, LinkageError, ThreadDeath továbbmegy.
  • Mire jó: exceptiont dobó külső hívás becsomagolására. Tipizált domain hibára az Either jobb.
  • Try.withResources a try-with-resources megfelelője.

Either (right-biased)

Either<LedgerError, Receipt> r =
    findAccount(id)                       // Either<LedgerError, Account>
        .flatMap(a -> debit(a, amt))      // Left esetén kimarad
        .map(Receipt::of);

r.fold(this::toProblemDetail, Receipt::toResponse);   // boundary
  • Left = hiba, Right = siker.
  • flatMap fail-fast: az első Left megszakítja a láncot.
  • Mikor: várt üzleti hiba (insufficient funds). Exception a váratlan technikai hibára.
  • Hasznos metódusok:
  • map / mapLeft / bimap(fl, fr)
  • filterOrElse(pred, r -> err): Right-ból Left, ha a feltétel nem teljesül
  • orElse(() -> alt): fallback
  • swap(): oldalak felcserélése
  • peek / peekLeft: side effect (logolás)
  • getOrElseThrow(l -> new XException(l)): vissza a kivételvilágba

Validation (error accumulation)

Validation<Seq<String>, Transfer> v = Validation.combine(
    validAmount(amt), validAccount(from), validAccount(to)
).ap(Transfer::new);
// Invalid(List("amount <= 0", "unknown account"))  <- minden hiba együtt
  • Either = fail-fast, Validation = accumulate (applicative).
  • Függő lépések → Either + flatMap. Független ellenőrzések → Validation.combine.

Átjárhatóság (konverziók)

Honnan → hova Hogyan
Optional → Option Option.ofOptional(opt)
Option → Optional .toJavaOptional()
Option → Either .toEither(() -> err) (a None-hoz kell hiba)
Option → Try .toTry()
Try → Either .toEither() (Left = Throwable), utána mapLeft
Either → Option .toOption() (a Left eltűnik)
Either ↔ Validation Validation.fromEither(e) / validation.toEither()

Vegyes lánc (példa)

Either<LedgerError, Receipt> transfer(String id, BigDecimal amt) {
  return Option.ofOptional(repo.findById(id))            // Optional -> Option
      .<LedgerError>toEither(() -> new NotFound(id))     // Option -> Either
      .flatMap(acc -> Try.of(() -> gateway.debit(acc, amt))
          .toEither()                                    // Try -> Either<Throwable, ...>
          .<LedgerError>mapLeft(GatewayError::new))      // hibatípus egységesítése
      .map(Receipt::of);
}
// nincs account  -> Left(NotFound)
// gateway hiba   -> Left(GatewayError)
// siker          -> Right(Receipt)

Gotchák

  • Either<L,R> invariáns L-ben: a flatMap azonos L-t vár. Megoldás: sealed LedgerError hierarchia + mapLeft vagy type witness (.<LedgerError>toEither(...)).
  • @Transactional csak kivételre rollbackel, Left-re nem. Vagy dobsz a boundary-nél, vagy setRollbackOnly(). Ledger-doménben klasszikus bug.
  • Either → Option információvesztés.
  • Ne keverd a hibakezelési stílusokat egy rétegben. Belül Either, a határon fold vagy throw.

Sequence (több elem együtt)

  • Option.sequence, Try.sequence: Iterable<Option<T>> → Option<Seq<T>>. Egy None vagy Failure elbuktatja az egészet.
  • Either.sequenceRight: fail-fast, az első Left-tel tér vissza. Either.sequence: az összes Left-et gyűjti.
  • traverse ugyanez mappeléssel. Pontos szignatúra: API doc.

Persistent collections

List<Integer> a = List.of(1, 2, 3);   // io.vavr.collection.List
List<Integer> b = a.prepend(0);       // O(1), a változatlan, b megosztja a farkát
java.util.List<Integer> j = b.toJavaList();
  • Immutable + structural sharing: módosítás = új példány, a régi rész megosztva marad.
  • List (linked), Vector, HashMap/HashSet, TreeMap, lazy Stream.
  • Nem java.util.Collection, konverzió: toJavaList(), toJavaMap() stb.
  • JDK-val összevetve:
  • List.of (JDK): immutable, de módosításhoz teljes másolat kell
  • Collections.unmodifiableList: csak view, az eredeti módosítható marad
  • Vavr: hatékony immutable módosítás

Egyéb

  • Tuple1..8, Lazy (memoized supplier), Function2.memoized(), curried(), andThen.
  • Match(x).of(Case($(...), ...)): elavult, a JDK switch pattern matching váltja.

JDK alternatíva: Result sealed + record

sealed interface Result<T> permits Ok, Err {}
record Ok<T>(T value) implements Result<T> {}
record Err<T>(LedgerError error) implements Result<T> {}

String msg = switch (result) {
    case Ok<Receipt>(var v)  -> "OK " + v.id();
    case Err<Receipt>(var e) -> "ERR " + e;
};
  • Előny: nincs dependency, exhaustiveness check a fordítótól.
  • Hátrány: nincs map/flatMap/sequence, ezeket magadnak kell megírni.

Hol találkozol Vavr-ral

  • Resilience4j (belül használja).
  • Spring Data: Vavr collection return type támogatás.
  • vavr-jackson: Jackson modul Vavr típusokhoz.