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)
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
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)
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.
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)
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);