Hacker News new | past | comments | ask | show | jobs | submit login

Yes, and I think Haskell monads are somewhat of an outlier in the way they're usually tackled. It's almost as interesting to learn exactly why Javascript promises are not monads, but you won't find any tutorials about Javascript promises that start this way, you just learn how to use promises.



Why are JS Promises not monads? Sincere question.


Monads are a particular pattern for function chaining. In OO terminology it is more like an interface than an object, so I would prefer to say that an object support a monad pattern rather than an object is a monad.

JS promises also supports chaining, but not in the same way as the monad pattern.

The Array.flatMap() method implements the monad pattern. The signature (in pseudo-typescript notation) would be:

   Array<T>.flatMap(T => Array<T>): Array<T>
I.e the argument is a higher order function which return the same type, and the function also return the same type. This gives unique flexibility in chaining functions, because this:

   [1, 2, 3].flatMap(x => [x + 1]).flatMap(x => x < 4 ? [x] : [])
has the same result as this:

   [1, 2, 3].flatMap(x => [x + 1].flatMap(x => x < 4 ? [x] : []))
So the functions can be chained or nested, arbitrarily, without affecting the result.

The above is equivalent to:

   [1,2,3].map(x => x + 1).filter(x => x < 4)
   
Which also use method chaining, but not in the same flexible way - you cannot change the chaining to nesting and still get the same result. So not every form of method chaining conform to the monad pattern. (For better or worse - map/filter is clearly easier to read and write, just not as composable.)

Note that the definition of monad says nothing about what the semantics of the chaining method (flatMap() in this case) actually is. It only defines the rules for how they can be chained.

Generalized, the signature is:

   Foo<T>.bind(T => Foo<T>): Foo<T>
(The method can be called anything, as long as it support the pattern. Traditionally it is called "bind".)

As far as I know, promises does not support a similar interface.


Great comment.

I think you are hitting the issue with monads tutorials. Most tutorials focus (or at that's where the reader will linger) on the flatMap "interface" and the box-or-container-of-things while mentioning monads laws only in passing.

So the reader is left wondering what's the big deal with an IFlatMappable interface other than, yes, it is a common pattern seen in the wild.

But the real power are the laws and the transformations. I suspect that for most procedural (with a sprinkle of FP) code those laws probably don't give you much, and if really needed the programmer might just derive the the specific transformation by local reasoning. An initiate of the FP arts will have internalized the transformations and will structure their code to take advantage of them as much as possible.

I say this as a boring procedural programmer that most definitely has not internalized monad laws.

As an aside, is it possible to further generalize monads and allow flatMap to interoperate with any monad instead of the same monad of its input? For example here the internal flatmap function might return an optional instead:

   [1, 2, 3].flatMap(x => [x + 1].flatMap(x => x < 4 ? Just(x) : None))


Promise<Promise<T>> is collapsed to Promise<T> automatically, which violates monad laws.


What’s interesting is this seems to say the exact opposite of what the other direct response says. But I also don’t think this is exactly true.

    Promise.resolve(
      Promise.resolve('foo')
    )
At runtime is Promise<Promise<string>>. It only collapses when chained to then (or await’ed), just as in the sibling comment’s flatMap example.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: