Jethro Larson

Posted on

# 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));
``````

In TypeScript:

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

Jethro Larson

If you want to use this with your team you may find the name "on" to be more palatable.

``````people.sort(on(getName)(compareLocale))
``````

or uncurrying it

``````people.sort(on(getName, compareLocale))
``````

though this makes it less flexible.