Sparround

Result vs Either (fpdart)

Either funksional proqramlaşdırmadan gəlir və Flutter cəmiyyətindəki "clean architecture" şablonlarında geniş yayılıb. Dart-da onun ən çox işlədilən implementasiyası fpdart paketidir.

Qaydası konvensiyadır: `Left` — uğursuzluq, `Right` — uğur. Yəni Either<Failure, List<Order>> "ya Failure, ya sifariş siyahısı" deməkdir.

Struktur baxımından Either ilə Result eyni ideyadır: iki halı bir tipdə birləşdirmək və çağıranı hər ikisini nəzərə almağa məcbur etmək. Fərq detallarındadır:

  • Result Flutter-in rəsmi dizayn pattern-idir, layihənin öz faylında yaşayır (10-15 sətir), asılılıq tələb etmir və Dart-ın öz switch-i ilə açılır.
  • Either paket gətirir, əvəzində zəngin kompozisiya API-si verir: map, mapLeft, flatMap, getOrElse, match, tryCatch. fpdart həm də Option, Task, TaskEither kimi əlaqəli tiplər gətirir — TaskEither "uğursuz ola bilən asinxron əməliyyat" deməkdir.

Seçim texniki üstünlük məsələsi deyil, komanda və layihə məsələsidir.

dart
// ══════ RESULT (rəsmi pattern) ══════
abstract interface class OrderRepository {
  Future<Result<List<Order>>> fetchMine();
}

class OrderRepositoryRemote implements OrderRepository {
  @override
  Future<Result<List<Order>>> fetchMine() async {
    try {
      final dtos = await _api.getOrders();
      return Result.ok(dtos.map((d) => d.toDomain()).toList());
    } on SocketException {
      return const Result.error(NetworkFailure());
    }
  }
}

// Notifier-də:
state = switch (await _repository.fetchMine()) {
  Ok(:final value) => OrdersState.loaded(value),
  Error(:final error) => OrdersState.failed(error),
};


// ══════ EITHER (fpdart) ══════
import 'package:fpdart/fpdart.dart';

abstract interface class OrderRepository {
  // Left = Failure, Right = uğur. Sıra konvensiyadır.
  Future<Either<Failure, List<Order>>> fetchMine();
}

class OrderRepositoryRemote implements OrderRepository {
  @override
  Future<Either<Failure, List<Order>>> fetchMine() async {
    try {
      final dtos = await _api.getOrders();
      // `Either.of` Right (uğur) yaradır.
      return Either.of(dtos.map((d) => d.toDomain()).toList());
    } on SocketException {
      return Either.left(const NetworkFailure());
    }
  }
}

// Notifier-də, variant 1 — fpdart-ın `match`-i:
state = (await _repository.fetchMine()).match(
  (failure) => OrdersState.failed(failure),
  (orders) => OrdersState.loaded(orders),
);

// Notifier-də, variant 2 — Dart-ın öz pattern-ləri:
state = switch (await _repository.fetchMine()) {
  Left(value: final failure) => OrdersState.failed(failure),
  Right(value: final orders) => OrdersState.loaded(orders),
};


// ══════ EITHER-in əsl gücü: kompozisiya ══════
// Ardıcıl əməliyyatlar `flatMap` ilə zəncirlənir; ilk uğursuzluqda
// zəncir dayanır və `Left` dəyəri sona qədər daşınır.
Future<Either<Failure, Receipt>> checkout(Cart cart) async {
  final reserved = await _inventory.reserve(cart);

  return reserved.flatMap((reservation) {
    // Bu blok yalnız `reserved` Right olduqda işləyir.
    return _pricing
        .quote(reservation)
        .map((quote) => Receipt(reservation: reservation, quote: quote));
  });
}

// Müqayisə üçün: `mapLeft` xətanı çevirir, dəyərə toxunmur.
final Either<String, List<Order>> forLogging =
    (await _repository.fetchMine()).mapLeft((f) => f.runtimeType.toString());

Eyni repository iki yanaşma ilə. Diqqət et: fpdart-ın `Either`-i də Dart-ın pattern-ləri ilə açıla bilir.

Meyar`Result` (rəsmi)`Either` (fpdart)
AsılılıqYoxdur — 15 sətirlik öz faylın`fpdart` paketi
Rəsmi mövqeFlutter sənədlərinin dizayn pattern-iRəsmi sənədlərdə yoxdur
Oxunaqlıq (yeni komanda üzvü)`Ok` / `Error` — dərhal aydındır`Left` / `Right` — konvensiyanı bilmək lazımdır
Kompozisiya (zəncirləmə)Əl ilə: `switch` + erkən `return``flatMap`, `map`, `mapLeft` — güclü tərəfi
Xəta tipi`Exception` (rəsmi implementasiyada)İstənilən tip: `Either<Failure, T>`
Əlavə tiplərYoxdur`Option`, `Task`, `TaskEither` və s.
Öyrənmə əyrisiPraktik olaraq yoxdurFP anlayışları (functor, monad) ilə tanışlıq faydalıdır

Tövsiyə. Bu branch-ın stack-ində (Riverpod + freezed) Result daha yaxşı uyğun gəlir və səbəblər konkretdir:

1. Rəsmi pattern: Flutter sənədlərində sənədləşdirilib, ona görə komanda mübahisəsi qısa olur. 2. Asılılıq yoxdur: 15 sətirlik fayl. Paket versiyası, breaking change, pub outdated problemi yoxdur. 3. `AsyncValue` ilə üst-üstə düşmə azdır: Riverpod-un AsyncValue-u artıq loading/error/data modelləşdirir; Result yalnız yazma əməliyyatlarında lazım olur. Bunun üçün paket gətirmək bahalı görünür. 4. `switch` kifayətdir: Dart 3-ün pattern-ləri Left/Right üçün də işləyir, ona görə fpdart-ın API-sinin əsas hissəsi (match, fold) artıq dildə var.

Either-in haqlı olduğu hallar da var:

  • Komanda FP ilə rahatdır və flatMap zəncirləri gündəlik dildir.
  • Çox addımlı, ardıcıl uğursuzluqlar olan axınlar (validasiya → hesablama → yazma) — flatMap orada switch yığınından qat-qat qısadır.
  • Layihədə artıq fpdart var və Option kimi tiplər işlədilir.

Ən pis seçim — ikisini birlikdə işlətmək: bir repository Result, digəri Either qaytarır. O zaman hər yeni fayl üçün "burada hansı?" sualı yaranır və çevirmə funksiyaları yazılır.

Miqrasiya barədə praktik qeyd: Either-dən Result-a (ya da əksinə) keçid mexanikidir və bir dəfə edilməlidir. Ən sadə yol — sərhədləri saymaqla başlamaq: grep -rn "Either<" lib | wc -l. Rəqəm 20-dən azdırsa, keçid bir günlük işdir; 200-dürsə, tədricən (repository-bir-repository) etmək lazımdır və bu müddətdə çevirici funksiya (Either<Failure,T> → Result<T>) saxlanılır.

Praktika. Eyni repository metodunu iki cür yaz: ResultEither (fpdart-ı dev_dependencies-ə əlavə edib sınamaq kifayətdir). Sonra ikisinə də notifier tərəfindən istifadə yaz və özün üçün qərar ver.

Hazır sayılır: hər iki variant kompilyasiya olunur, və ARCHITECTURE.md-də bir sətirlik qərar yazılıb: "layihədə xəta axını üçün X işlədilir, çünki ...".

📚 Mənbələr və sənədlər

  • fpdart paketirəsmipub.dev

    `Either`, `Option`, `Task`, `TaskEither` və onların API-si — `Either.of`, `Either.left`, `map`, `mapLeft`, `flatMap`, `match`.

  • Result obyektləri ilə xəta idarəsirəsmidocs.flutter.dev

    Rəsmi alternativ — və onun niyə asılılıq tələb etmədiyi.

  • Dart: pattern-lərrəsmidart.dev

    `Left(value: final f)` kimi obyekt pattern-ləri — fpdart ilə də işləyir.