Either<L, R>.binding constructor

Either<L, R>.binding(
  1. @monadComprehensions R block(
    1. EitherEffect<L> effect
    )
)

Monad comprehension. Syntactic sugar do-notation. Although using FlatMapEitherExtension.flatMap openly often makes sense, many programmers prefer a syntax that mimics imperative statements (called do-notation in Haskell, perform-notation in OCaml, computation expressions in F#, and for comprehension in Scala). This is only syntactic sugar that disguises a monadic pipeline as a code block.

Calls block with a scope-bound EitherEffect and returns its Either.

Inside block, effect.bind(either) returns the Right.value of either. Binding a Left immediately terminates this binding scope and returns that Left. BindEitherExtension.bind provides the equivalent either.bind(effect) syntax. When there is no Right value to extract, RaiseEitherEffectExtension.raise short-circuits directly with a left value without first constructing a Left solely to bind it.

Each invocation owns a distinct scope. Nested binding scopes therefore catch only their own short-circuit. The capability is valid only while block is running; invoking a captured capability after block returns throws a StateError. Ordinary exceptions propagate unchanged.

Example

class ExampleError {}

Either<ExampleError, int> provideX() { ... }
Either<ExampleError, int> provideY() { ... }
Either<ExampleError, int> provideZ(int x, int y) { ... }

final result = Either<ExampleError, int>.binding((effect) {
  final int x = provideX().bind(effect);
  final int y = effect.bind(provideY());
  final int z = provideZ(x, y).bind(effect);
  return z;
});

NOTE

/// This function can throw an error.
int canThrowAnError() { ... }

// DON'T
final badResult = Either<ExampleError, int>.binding((_) {
  final int value = canThrowAnError();
  return value;
});

// DO
ExampleError toExampleError(Object e, StackTrace st) { ... }

final result = Either<ExampleError, int>.binding((effect) {
  final int value = Either<ExampleError, int>.tryCatch(
    action: canThrowAnError,
    errorMapper: toExampleError,
  ).bind(effect);
  return value;
});

Implementation

factory Either.binding(
    @monadComprehensions R Function(EitherEffect<L> effect) block) {
  final EitherEffect<L> effect = _BindingScope<Never Function(L)>._(_Token());

  try {
    final value = block(effect);
    effect._throwIfRaised();

    return Either.right(value);
  } on ControlError<L> catch (e) {
    if (identical(effect._token, e._token)) {
      return Either.left(e._value);
    } else {
      rethrow;
    }
  } finally {
    effect._close();
  }
}