Either<L, R>.binding constructor
- @monadComprehensions R block(
- EitherEffect<
L> effect
- EitherEffect<
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
- Do NOT catch ControlError in
block. - Do NOT store EitherEffect or invoke it after
blockreturns. - Errors thrown by
blockare not converted to Left. - Use Either.tryCatch, Either.tryCatchAsync or ToEitherStreamExtension.toEitherStream to catch error, then bind the resulting Either.
/// 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();
}
}