Using library functions instead of fn() lambdas

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.

Functions used

Why bother?

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.

1. Property equals a value

// 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.

2. Not null

// 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.

3. Negate any predicate

// 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'.

4. Numeric comparisons

// inline:
fn($n) => $n > 100

// library:
C\isGreaterThan(100)

Same shape for isLessThan, isGreaterThanOrEqualTo, isLessThanOrEqualTo, isEqualTo.

5. “Field of a record passes a comparison” — compose two library pieces

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

6. Multiple-of / divisibility

// inline:
fn($n) => $n % 2 === 0

// library:
N\isMultipleOf(2)

Same pattern for isFactorOf. Instantly readable inside a filter chain.

7. Substring check

// 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.

8. String transforms

// 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.

9. Combining predicates with AND / OR

// 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.

10. Everything together — a filter chain

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:

When to still use a lambda

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.