Predicates
A predicate is a pure function that accepts any arguments and returns a boolean.
const isEven = (n: number) => n % 2 === 0;
console.log(isEven(2)); // true
Predicates are fundamental in programming, and their usage is widespread across various paradigms.
Higher-order Functions
A higher-order function is a function that accepts arguments and returns a specialized (partially-applied) function.
const times = (m: number) => (n: number) => n * m;
// e.g. inline:
const doubledInline = numbers.map(times(2));
// e.g.:
const double = times(2);
const doubled = numbers.map(double);
When the outermost function is invoked, it returns a new function with the first argument "baked in," awaiting the second argument. This is known as currying.
Currying enables partially-applied functions to be stored in variables or used inline. In both cases, it simplifies extraction of functions that would otherwise require explicit inline parameters. Those functions can now be used point-free.
Point-free Style
Point-free programming has nothing to do with points in a geometric sense or with the dot-syntax. Here, a "point" refers to an argument.
const doubledAgain = numbers.map((n) => n * 2);
In this example, n
is the "point."
This style introduces some issues:
- The
2
is hardcoded. The only alternative without resorting to a higher-order function (HoF) would be to retrieve it from scope, compromising purity. - It is less readable compared to previous examples.
Consider the earlier examples:
const doubledInline = numbers.map(times(2));
// ...
const doubled = numbers.map(double);
In both cases, the .map()
calls are point-free. Using a higher-order function (HoF) allows the 2
to be embedded directly into the function—either inline (times(2)
) or pre-stored as a partially-applied function (double
).
Higher-Order Predicates: The Key to 10x Readability and Composition
A higher-order predicate is a function that accepts arguments and returns a specialized predicate.
By using higher-order predicates, you can achieve the same level of readability and reusability as seen with mappers, but applied to filter functions (or similar methods like find
, every
, and some
).
const isDivisibleBy = (divisor: number) => (dividend: number) => dividend % divisor === 0;
[1, 2, 3, 4].filter(isDivisibleBy(2)); // [2, 4]
This approach encourages the creation and organization of reusable predicates and higher-order predicates into semantic groupings, enhancing discoverability and readability. Additionally, they are straightforward to unit test.
For a ready-to-use library, consider fp-filters, a curated collection of 130+ predicates. It is fully typed, 100% tested, and open for contributions.
Top comments (0)