fantasyland / fantasy-land

Specification for interoperability of common algebraic structures in JavaScript
MIT License
10.08k stars 373 forks source link

Implementation of `Compose` is confusing #332

Closed somebody1234 closed 1 year ago

somebody1234 commented 2 years ago

I'm wondering how the implementation of Compose works? Assuming f is Compose (a -> b) then f.c must be a -> b. However, that would mean f.c['fantasy-land/map'] is undefined (unless Functor is implemented for functions)?

Am I just misunderstanding something here, or...?

gabejohnson commented 2 years ago

Functions in JavaScript have a type Function (a | null | undefined) (b | null | undefined), but lets say Function a b for simplicity's sake. While Function a b doesn't have a Functor instance, Function SomeType b does. It can be defined as follows:

Function.prototype['fantasy-land/map'] = function (f) {
  var g = this;
  return function (a) {
    return f(g(a));
  };
};

// usage
// `Function Number Number` composed with `Function Object String` => `Function Number String` or `(Number -> String)`
(x => x + 1)['fantasy-land/map'](x => x.toString())(42); // '43'

As evidenced by the result, you can see that Function.prototype['fantasy-land/map'] is equivalent to Function.prototype['fantasy-land/compose'].

somebody1234 commented 2 years ago

Ah... makes sense I guess, but surely it'd be clearer to just write it as normal composition rather than [map]?

gabejohnson commented 2 years ago

but surely it'd be clearer to just write it as normal composition rather than [map]?

Yes if you're composing two functions in application code. However, defining an Traversable instance for Compose f g requires Functor instances for both f and g. The Traversable instance is defined in terms of those Functor instances.

Perhaps it would be less confusing if an implementation of Traversable (Compose f g) were provided in the Traversable example.