λ1011: Prefer Select over Match
A Match that keeps the failure state and transforms the value should be expressed as Select.
Cause
Match is called on an Option<T>, Either<L, R> or Result<T> where the failure branch returns the monad's own
empty or error state (Option<TResult>.None, Either<L, TResult>.Left, Result<TResult>.Error) and the success branch
returns a plain value of the inner type.
When the success branch returns a monad itself, the call re-implements SelectMany and λ1007 reports it
instead.
Reason for rule
Match is the most general way to leave a monad; it is the escape hatch that every other operation could be written with.
When the failure branch merely passes the failure state on and the success branch maps the value, the call re-implements
Select. Using the dedicated method names the intent, drops the repeated failure branch and keeps the type parameter
inferred.
How to fix violations
Replace the Match call with Select, passing the former some/right/ok argument. A code fix is available.
Examples
Disallowed
static Option<string> Example(Option<int> option)
=> option.Match(none: Option<string>.None, some: x => x.ToString());
static Either<string, int> Example(Either<string, int> either)
=> either.Match(left: Either<string, int>.Left, right: x => x * 2);
static Result<string> Example(Result<int> result)
=> result.Match(error: Result<string>.Error, ok: x => x.ToString());
Allowed
static Option<string> Example(Option<int> option)
=> option.Select(x => x.ToString());
static Either<string, int> Example(Either<string, int> either)
=> either.Select(x => x * 2);
static Result<string> Example(Result<int> result)
=> result.Select(x => x.ToString());
// The failure branch produces a value, so this is a legitimate Match.
static Option<int> Example(Option<int> option, Option<int> fallback)
=> option.Match(none: fallback, some: x => x + 10);