DEV Community

Iven Marquardt
Iven Marquardt

Posted on • Updated on

Composing binary functions with a fixed return type - wait, what?

Functonal programming is about composition. Ordinary composition of single-argument functions is trivial. It gets more interesting if we try to combine more complex function types of the real world. What about composing functions that compare two values and return a comparator?

First we don't want to rely on the 1/0/-1 comparator protocol but on a real tagged union:

const Comparator = union("Comparator");

const LT = Comparator("LT", {valueOf: () => -1});

const EQ = Comparator("EQ", {valueOf: () => 0});

const GT = Comparator("GT", {valueOf: () => 1});

// monoid instance

const ctorEmpty = () => EQ;

const ctorAppend = tx => ty => 
  match(tx, {
    LT: _ => LT,
    EQ: _ => ty,
    GT: _ => GT
  });

Next we need a Compare type for functions that return a Comparator:

const Compare = cmp => record(Compare, {cmp});

// monoid instance

const cmpEmpty = () => _ => _ => ctorEmpty();

const cmpAppend = tx => ty =>
  Compare(x => y => ctorAppend(tx.cmp(x) (y)) (ty.cmp(x) (y)));

Now we can combine several Compare based functions to define more complex comparing rules. We can do this because we implemented the monoid instances for both types:

const isEven = x => (x & 1) === 0;

const byNatural = Compare(x => y =>
  x < y ? LT
    : x > y ? GT
    : EQ);

const byEven = Compare(x => y =>
  isEven(x) && !isEven(y) ? GT
    : !isEven(x) && isEven(y) ? LT
    : EQ);

const xs = [4, 8, 2, 5, 3, 9, 6, 1, 7, 0];

xs.sort(
  (x, y) =>
    cmpAppend(byEven)
      (byNatural).cmp(x) (y)); // [1, 3, 5, 7, 9, 0, 2, 4, 6, 8]

run code

We use a destructive sort function, but that is okay for the time being.

Top comments (0)