Rubens Pinheiro Gonçalves Cavalcante
1.1K6

Hello Rubens, cool article! Functional programming and category theory in JavaScript is so much fun!

That Symbol.species tricks is extremly cool by the way, I’ll keep it for later.

However, I’m not sure if I understandd correctly your explanation about monads (the quote from Mr Eliott about the characteristics of a monad are not very clear to me).

I get that a monad is a functor (it has a `map` method) with a `flatMap` function and a `of` function that let you wrap a value with monad.

What bugs me in your Zord monad is that the flatMap function can unwrap/flatten of as many levels as needed in order to have access to the wrapped primitive value.

`(flatMap/chain/bind) :: Monad a -> (a -> Monad b) -> Monad b`

flatMap expects a function `(a -> Monad b)` that takes a value (the value wrapped inside the monad) and returns a value wrapped in the said monad.

In the case of Zord, I would have been :

`// Let's wrap a color inside a Zordconst z1 = Zord.of('blue')`
`const z2 = z1.flatMap(color => {  console.log(color) // -> 'blue'`
`  const newColor = addColors(color, 'red')     console.log(newColor) // -> 'purple'    return Zord.of(newColor)})`
`// z2 is a new Zord instance wrapping 'purple'`

But if we have more levels of nesting :

`const z1 = Zord.of(Zord.of(Zord.of('blue')))`
`console.log(z1) // -> Zord(Zord(Zord('blue')))`
`const z2 = z1.flatMap(value => {  console.log(value) // -> 'Zord(Zord(blue))'  return value})`
`console.log(z2) // -> Zord(Zord('blue'))`

Here, we are under the impression that the data structure has been flattened of one level, but this behavior is caused by the actual definition of flatMap. This case is explicitely the case where we pass the identity function `x => x` to flatMap.

In fact according, to the monad definition here, `flatMap(x => x)` is equivalent to the `join` function that flattens one level of nesting i.e.

`join :: Monad (Monad a) -> Monad a`
`const z = Zord.of(Zord.of(Zord.of('blue')))`
`z.join() // Zord(Zord('blue'))z.flatMap(x => x) // Zord(Zord('blue'))`
`z.join().join() // Zord('blue')z.flatMap(x => x).flatMap(x => x) // Zord('blue')`

If I reuse the code snippet from your article, I think that we should get :

`Zord.of('white')    .flatMap(rng => console.log(rng)); // logs white`
`Zord.of(Zord.of('white'))    .flatMap(rng => console.log(rng)); // logs Zord(white) <--- ?`

In the case of Promises, there is an exception because `Promise.resolve` does not behaves like the `Monad.of` function of the definition of the monad, it flattens promises automagically :) and `.then` conflates the behaviors of `.map` and `.flatMap` .

`Promise.resolve('hello').then(res => console.log(res)) // -> 'hello'`
`Promise.resolve(Promise.resolve(Promise.resolve('hello'))).then(res => console.log(res)) // -> 'hello' and not Promise(Promise('hello'))`

So I think Promise can be called a ‘special’ monad in JavaScript :)

`// .then behaves like .mapPromise.resolve(42).then(x => x + 1).then(x => console.log(x)) --> 43`
`// .then behaves like .flatMapPromise.resolve(42).then(x => Promise.resolve(x + 1)).then(x => console.log(x)) --> 43`

Phew! What a wall of text …