Beautiful Functions: Psi
Continuing on with my last post, I want to look at another function which I consider particularly elegant, the psi combinator:
const psi = (f) => (g) => (x, y) => g(f(x), f(y));
const psi = <A, B>(f: (x: A) => B) => <C>(g: (x: B, y: B) => C) => (x: A, y: A): C => g(f(x), f(y));
This is also called
on in Haskell.
What it does is map a function over both arguments of a binary(two-argument) function. This is similar to the
B combinator but changed to work on binary functions.
The quintessential usage is when sorting records:
// given a compare function const localeCompare = (item1: string, item2: string): number => item1.localeCompare(item2); // and some accessor function that drills into a data structure const getName = (person) => person.name; // compose that accessor with the compare function to drill both sides const compareNames = psi(getName)(localeCompare); // which can be passed to a sort method for an array of that structure people.sort(compareNames)
Interestingly, this is equivalent to doing a map and then sort, but using psi is theoretically more memory efficient:
// generates an extra array people.map(getName).sort(localeCompare)
Look out for other opportunities to use
psi and I'm sure you'll find them. Particularly if you're doing data aggregation or processing.
Pedantic Disclaimer: Psi is usually defined with the binary function as the first argument but I preferred the similarity to
B combinator when taking the mapping function as the first.