λ1018: Prefer Flatten over SelectMany with the identity function
SelectMany(x => x) collapses one level of nesting. Flatten() does the same and says so by name.
Cause
SelectMany is called with the identity function, x => x or Identity from Funcky.Functional, on a nested
Option<Option<T>>, Result<Result<T>>, Either<L, Either<L, R>> or IEnumerable<IEnumerable<T>>. Only the SelectMany
of the monads and of System.Linq.Enumerable counts: ParallelEnumerable and Queryable have their own, which Flatten
would turn into a plain enumerable.
The overload with a result selector, query syntax (from x in xs from y in x select y) and SelectMany inside an
expression tree are not reported.
Reason for rule
A selector that returns its argument is noise: the reader has to look at the lambda to find out that it does nothing.
Flatten states the intent and needs no selector. λ1017 would otherwise turn the lambda into SelectMany(Identity),
which is still a roundabout way of writing Flatten().
How to fix violations
Replace the SelectMany call with Flatten(). The code fix does this and adds the using Funcky.Monads; or
using Funcky.Extensions; when the extension method is not yet in scope. Explicit type arguments select the element type
and are kept: SelectMany<IEnumerable<string>, object>(x => x) becomes Flatten<object>(). The fix is only offered when
the rewritten call reaches Funcky's Flatten; a Flatten member of the receiver's own type would win over the extension
method, so there the call is reported without a fix.
Examples
Disallowed
static Option<int> Example(Option<Option<int>> option)
=> option.SelectMany(x => x);
static IEnumerable<int> Example(IEnumerable<IEnumerable<int>> values)
=> values.SelectMany(Identity);
Allowed
static Option<int> Example(Option<Option<int>> option)
=> option.Flatten();
static IEnumerable<int> Example(IEnumerable<IEnumerable<int>> values)
=> values.Flatten();
// The selector does something.
static Option<int> Example(Option<Option<int>> option)
=> option.SelectMany(inner => inner.Select(x => x + 1));