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.
In Haskell, everything is a function. In your javascript, some of your functions are represented as func
s with an .x()
method, and some are native Function
s. 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 Function
s, 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