A practical reference for swapping inline arrow-function predicates for composable library calls. Shorter, more reusable, and — most importantly — named for what they do instead of how they do it.
An fn() lambda is small and fast to write. But six months later, scanning a filter chain, you’re reading predicates instead of names. The whole point of this library is to give you named, reusable predicates and transforms so the code reads like a specification.
The patterns below are the most common offenders. Each swap is a one-liner and the right-hand side is usually shorter than the lambda anyway.
// inline:
fn($user) => $user['role'] === 'admin'
// library:
F\propertyEquals('role', 'admin')propertyEquals works on arrays and objects alike. Bind the key and the expected value up front; reuse across any collection.
// inline:
fn($v) => $v !== null
// library:
C\not('is_null')is_null is a built-in PHP function; as a string it’s a valid callable, so C\not('is_null') composes cleanly into filters. No wrapper needed.
// inline:
fn($x) => ! $isAdmin($x)
// library:
C\not($isAdmin)Works for predicates defined anywhere — your own Closures, imported library predicates, even PHP’s built-ins like 'is_numeric'.
// inline:
fn($n) => $n > 100
// library:
C\isGreaterThan(100)Same shape for isLessThan, isGreaterThanOrEqualTo, isLessThanOrEqualTo, isEqualTo.
// inline:
fn($order) => $order['total'] >= 100
// library:
F\compose(F\getProperty('total'), C\isGreaterThanOrEqualTo(100))Longer on one line but each piece is a named concept you can reuse. Common enough that you’ll name the result:
$isHighValue = F\compose(F\getProperty('total'), C\isGreaterThanOrEqualTo(100));
// Now everywhere that used the inline version becomes:
array_filter($orders, $isHighValue);// inline:
fn($n) => $n % 2 === 0
// library:
N\isMultipleOf(2)Same pattern for isFactorOf. Instantly readable inside a filter chain.
// inline:
fn($s) => str_contains($s, 'foo')
// library:
Str\contains('foo')startsWith, endsWith, containsPattern all follow the same shape — one library call instead of a lambda.
// inline:
fn($s) => str_replace(' ', '-', $s)
// library:
Str\replaceWith(' ', '-')Many PHP string functions need arguments in a specific order before the subject — replaceWith, append, prepend, trim, lTrim, rTrim, slice, pad, etc. all return a Closure that takes just the subject, perfect for drop-in into map/pipe.
// inline:
fn($user) => $user['active'] && $user['verified']
// library:
C\groupAnd(
F\propertyEquals('active', true),
F\propertyEquals('verified', true)
)Shorter for one-off, but the library version composes with other library predicates and lets you name the overall concept ($isTrustedUser). Same story for groupOr.
Before:
$highValueAdmins = array_filter($orders, fn($o) =>
$o['customer']['role'] === 'admin'
&& $o['total'] >= 100
&& $o['refunded'] !== true
);After — three named concepts, one composed predicate:
$isAdminOrder = F\compose(F\getProperty('customer'), F\propertyEquals('role', 'admin'));
$isHighValue = F\compose(F\getProperty('total'), C\isGreaterThanOrEqualTo(100));
$isRefunded = F\propertyEquals('refunded', true);
$isTargetOrder = C\groupAnd($isAdminOrder, $isHighValue, C\not($isRefunded));
$highValueAdmins = array_filter($orders, $isTargetOrder);Five lines instead of four, but:
$isAdminOrder, $isHighValue, $isRefunded are reusable across the codebase.Don’t force composition. If a predicate genuinely is one line of custom logic that won’t be reused, reaches into context (uses use (...) to capture outside variables), or involves arithmetic no library piece abstracts — an fn() is fine. The point is to reach for the library FIRST.