predicate combinators
Builds a condition out of named predicates — and, or, xor, negate, contramap — instead of nesting lambdas.
Lecture
Every filtering operator in the library takes a predicate:
filter,
reject,
takeWhile,
skipWhile,
countWhere,
partition. Once you have named
the conditions — isEven, isPositive,
isBlank — combining them should not cost you a fresh lambda
with a re-typed parameter each time. These combinators are that.
They are an extension on bool Function(T), so any predicate
you already have grows the methods: a top-level function, a tear-off, a
stored closure, or the result of another combinator. Each one returns a
new predicate and calls nothing until that predicate runs.
and and or short-circuit exactly like
&& and || — the right-hand predicate is
skipped when the left one has already decided, which matters when it is
the expensive half. xor has nothing to short-circuit and
always calls both.
contramap is the odd one and the useful one. It maps the
argument rather than the result — that is what the
contra means — so a predicate on int becomes a
predicate on anything you can turn into an int:
isEven.contramap<String>((s) => s.length) tests a
string's length without a word about strings in isEven.
.negate is the extension-getter form of the top-level
negate — the same function, reached
from the other side. Use whichever reads better at the call site;
isBlank.or(isShort).negate reads left to right, where
negate(...) would push the whole expression inside a call.
Demo 1 · and, or, xor, negate
Demo 2 · contramap, and short-circuiting
Try it yourself
Exercise: keep the rows that are neither blank nor short.
negate — the top-level form of .negate ·
not — flips a single boolean value, not a predicate ·
filter / whereNot — where a combined predicate usually lands ·
predicates — the built-in type predicates to combine with