Filtering and partitioning

LINQ's Where keeps or drops elements by a predicate. These operations cover the cases around it: filtering and transforming in one step when the transformation can fail (WhereSelect), removing null while fixing the type (WhereNotNull), keeping both halves instead of only one (Partition), asking whether anything is left (None), and substituting a fallback when nothing is (AnyOrElse).

WhereSelect

Select with a selector that may decline: the selector returns an Option<T>, and the Nones are dropped.

IEnumerable<TResult> WhereSelect<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, Option<TResult>> selector)
IEnumerable<TResult> WhereSelect<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, int, Option<TResult>> selector)
IEnumerable<TSource> WhereSelect<TSource>(this IEnumerable<Option<TSource>> source)
A marble diagram showing the WhereSelect operation

This is the operation that makes the …OrNone methods compose with sequences. Where you would otherwise write a Where to filter out the inputs that will fail, followed by a Select that transforms the rest, often duplicating the check, WhereSelect does both in one step with one function. In the "option is a list with at most one element" picture from the Option chapter, it is simply SelectMany.

The overload without a selector flattens a sequence of options, keeping the Some values. The indexed overload hands the selector the element's position as well, like the indexed Select.

WhereSelect is lazy. The selector is called once per element.

Example

Parsing what can be parsed:

var input = Sequence.Return("12", "abc", "7", "", "40");

input.WhereSelect(ParseExtensions.ParseInt32OrNone);
// [12, 7, 40]

Looking up what exists:

IEnumerable<User> FindUsers(IEnumerable<int> ids)
    => ids.WhereSelect(usersById.GetValueOrNone);

Keeping only the present values of a sequence of options:

var found = Sequence.Return(Option.Some(1), Option<int>.None, Option.Some(3)).WhereSelect();
// [1, 3]

WhereNotNull

Drops the null elements of a sequence, and narrows the element type from T? to T.

IEnumerable<TSource> WhereNotNull<TSource>(this IEnumerable<TSource?> source) where TSource : class
IEnumerable<TSource> WhereNotNull<TSource>(this IEnumerable<TSource?> source) where TSource : struct
A marble diagram showing the WhereNotNull operation

Where(x => x != null) removes the nulls but leaves the static type as IEnumerable<T?>, so every later use still needs a null check or a !. WhereNotNull changes the type, which is the whole point: the compiler knows the nulls are gone. It works for nullable reference types and for Nullable<T>, unwrapping the latter.

It is the bridge from APIs that return null into Funcky's notnull-constrained world, and the first step before FirstOrNone and friends on a sequence with nullable elements.

Example

IEnumerable<string?> lines = Sequence.Return("a", null, "b");
IEnumerable<string> nonNull = lines.WhereNotNull();
// ["a", "b"]

IEnumerable<int?> readings = Sequence.Return<int?>(1, null, 3);
IEnumerable<int> values = readings.WhereNotNull();
// [1, 3]

var envValues = names
    .Select(Environment.GetEnvironmentVariable)
    .WhereNotNull();

Partition

Splits a sequence into two lists in a single pass: the elements for which a predicate holds and those for which it does not. Further overloads split a sequence of Either or Result values by their case.

Partitions<TSource> Partition<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
TResult Partition<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, bool> predicate, Func<IReadOnlyList<TSource>, IReadOnlyList<TSource>, TResult> resultSelector)
A marble diagram showing the Partition operation

Partition is eager: it enumerates the whole input once and returns two materialized lists, in the original order. The alternative, Where(p) and Where(x => !p(x)), enumerates the input twice and evaluates the predicate twice per element. The result struct has True and False properties and deconstructs in that order. The result selector overload receives the two lists directly, which saves the intermediate struct when you only want a projection of them.

Example

var (evens, odds) = Enumerable.Range(1, 8).Partition(n => n % 2 == 0);
// evens: [2, 4, 6, 8]
// odds:  [1, 3, 5, 7]

var summary = orders.Partition(
    order => order.IsPaid,
    (paid, unpaid) => $"{paid.Count} paid, {unpaid.Count} outstanding");

Partitioning by Either and Result

A sequence of Either<L, R> splits into the left and the right values, and a sequence of Result<T> into the exceptions and the ok values. For Either there are also overloads that take the selector producing the eithers, so the Select can be folded into the Partition.

EitherPartitions<TLeft, TRight> Partition<TLeft, TRight>(this IEnumerable<Either<TLeft, TRight>> source)
EitherPartitions<TLeft, TRight> Partition<TSource, TLeft, TRight>(this IEnumerable<TSource> source, Func<TSource, Either<TLeft, TRight>> selector)
ResultPartitions<TValidResult> Partition<TValidResult>(this IEnumerable<Result<TValidResult>> source)

Each of these has a counterpart with a result selector receiving the two lists.

A marble diagram showing the Partition operation on a sequence of Either values

EitherPartitions deconstructs into (left, right), ResultPartitions into (error, ok). This is the "process everything and report all failures" counterpart to Sequence and Traverse, which stop at the first failure; see the Result chapter.

var (errors, orders) = requests
    .Select(request => PlaceOrder(request))   // each returns Result<Order>
    .Partition();

var (invalid, valid) = lines.Partition(line => ValidateLine(line));   // returns Either<ValidationError, Record>

None

Returns true if the sequence is empty, or if no element satisfies the predicate. It is !Any(), with the negation where it is easy to see.

bool None<TSource>(this IEnumerable<TSource> source)
bool None<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)

Like Any, None stops at the first element that decides the answer, so it is safe on infinite sequences as long as a matching element exists.

!items.Any(x => x.IsValid) reads as "not any valid" and is easy to confuse with items.Any(x => !x.IsValid), which means something else. items.None(x => x.IsValid) says what it checks.

Example

if (errors.None())
{
    Commit();
}

bool allSettled = payments.None(p => p.IsPending);

AnyOrElse

Returns the sequence itself if it has at least one element, otherwise a fallback sequence.

IEnumerable<TSource> AnyOrElse<TSource>(this IEnumerable<TSource> source, IEnumerable<TSource> fallback)
IEnumerable<TSource> AnyOrElse<TSource>(this IEnumerable<TSource> source, Func<IEnumerable<TSource>> fallback)
A marble diagram showing the AnyOrElse operation

This is OrElse from Option<T> for sequences, with "empty" playing the role of None. It replaces the items.Any() ? items : fallback idiom, which enumerates items twice and breaks on a one-shot iterator. AnyOrElse enumerates the source exactly once: it yields the elements as they come and only consults the fallback once the source has finished without producing anything. The Func overload defers building the fallback until it is actually needed.

DefaultIfEmpty from LINQ is the special case with a one-element fallback.

Example

var recipients = explicitRecipients.AnyOrElse(() => LoadDefaultRecipients());

var menu = todaysSpecials.AnyOrElse(standardMenu);