DEV Community

Jethro Larson
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));
Enter fullscreen mode Exit fullscreen mode

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));
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (1)

Collapse
 
jethrolarson profile image
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))
Enter fullscreen mode Exit fullscreen mode

or uncurrying it

people.sort(on(getName, compareLocale))
Enter fullscreen mode Exit fullscreen mode

though this makes it less flexible.