DEV Community

Viktor Pelle
Viktor Pelle

Posted on

Playing with contravariant functors in typescript

Lets define a sum type Ordering with its data constructors:

class LT {} class EQ {} class GT {} type Ordering = LT | EQ | GT; class Comparison<A> { constructor(public runComparison: ((a: A) => (b: A) => Ordering)){} } 
Enter fullscreen mode Exit fullscreen mode

Because of the position of the parameter we can see that this is a contravariant structure

Example usage:

  • We can compare numbers
const x = new Comparison<number>((a) => b => { if (a === b) return new EQ(); if (a > b) return new GT(); if (a < b) return new LT(); }) 
Enter fullscreen mode Exit fullscreen mode

Now lets define our contravariant functor over Comparison.

As you know functors map has a type of:

<A, B>(f: (a: A) => B) => (a: F A) => F B. 
Enter fullscreen mode Exit fullscreen mode

And while functors pack things (think of array, or function composition) the contravariant functors "unpack".
Its type is:

<A, B>(f: (b: B) => A) => (a: F A) => F B 
Enter fullscreen mode Exit fullscreen mode

For Comparison this is:

 type contramapT = <A, B>(f: (b: B) => A) => (a: Comparison<A>) => Comparison<B>; const contramap: contramapT = (f) => compC => { return new Comparison(a => b => compC.runComparison(f(a))(f(b))) } 
Enter fullscreen mode Exit fullscreen mode

Let say we have some users which age we would like to compare.

 const compareNumbers = new Comparison<number>((a) => b => { if (a === b) return new EQ(); if (a > b) return new GT(); if (a < b) return new LT(); }); type userT = { name: string; age: number; } const user1 = { name: "Igor", age: 25 } const user2 = { name: "Ivan", age: 28 } const userAgeComparer = contramap<number, userT>(a => { return a.age; })(compareNumbers) const result = userAgeComparer.runComparison(user1)(user2); 
Enter fullscreen mode Exit fullscreen mode

As you can see from this trivial example we generated a new comparer for our user.

Top comments (0)