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]
We use a destructive sort function, but that is okay for the time being.
Top comments (0)