Search code examples
javascriptfunctional-programmingcompositionfunctor

How to implement a functor so that map can be applied to two functions?


In Haskell you can apply fmap to two functions, which is basically function composition. You can even compose fmap to enable function composition of functions with higher arity (fmap . fmap).

This works because functions are functors.

How would such a functor (or the appropriate map method) be implemented in Javascript?

This is what I have tried so far:

funcProto = {
  map(f) { return y => f(this.x(y)) }
};

function func(x) {
  return Object.assign(Object.create(funcProto), {x: x});
}

const comp = f => g => x => f(g(x));
const map = f => ftor => ftor.map(f);
const sub = y => x => x - y;
const sqr = x => x * x;
const inc = x => x + 1;

This works for normal function composition:

func(sqr).map(inc)(2); // 5

However, it doesn't work for a composed version of map:

const map2 = comp(map)(map);
map2(sub)(sub)(10)(5)(4); // Error

I think I adapt myself too much to the traditional way functors are implemented in Javascript. Functions as functors behave differently from list or maybe.


Solution

  • In Haskell, everything is a function. In your javascript, some of your functions are represented as funcs with an .x() method, and some are native Functions. That cannot work.

    Here are three approaches:

    const sub = y => x => x - y;
    const sqr = x => x * x;
    const inc = x => x + 1;
    const comp = f => g => x => f(g(x));
    
    • plain functions, no methods.

      const fmap = comp; // for functions only
      console.log(fmap(inc)(sqr)(1)) // 5
      const fmap2 = comp(fmap)(fmap);
      console.log(fmap2(sub)(sub)(10)(5)(4)); // 9
      
    • extending native Functions, using fmap as a method:

      Function.prototype.fmap = function(f) { return comp(this)(f); };
      console.log(sqr.fmap(inc)(1)); // 5
      const fmap2 = comp.fmap(comp) // not exactly what you want, works just like above
      Function.prototype.fmap2 = function(f) { return this.fmap(g => g.fmap(f)); } // better
      console.log(sub.fmap2(sub)(10)(5)(4)); // 9
      
    • building your own function type (also in ES6):

      function Func(f) {
          if (!new.target) return new Func(f);
          this.call = f;
      }
      // Ahem.
      const sub = Func(y => Func(x => x - y));
      const sqr = Func(x => x * x);
      const inc = Func(x => x + 1);
      const comp = Func(f => Func(g => Func(x => f.call(g.call(x)))));
      // Now let's start
      const fmap = Func(f => Func(x => x.fmap(f))); // a typeclass!
      Func.prototype.fmap = function(f) { return comp(this)(f); }; // an instance of the class!
      console.log(fmap.call(inc).call(sqr).call(1)); // 5
      const fmap2 = comp.call(fmap).call(fmap);
      console.log(fmap2.call(sub).call(sub).call(10).call(5).call(4)); // 9