Implementing JavaScript Functors and Monads
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.

From what I read here (http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html) and here (https://en.wikibooks.org/wiki/Haskell/Category_theory#Monads), I don’t think your Zord flatMap method respect the monad flatMap signature i.e :

(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 Zord
const 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 .map
Promise.resolve(42)
.then(x => x + 1)
.then(x => console.log(x)) --> 43
// .then behaves like .flatMap
Promise.resolve(42)
.then(x => Promise.resolve(x + 1))
.then(x => console.log(x)) --> 43

Phew! What a wall of text …

Tell me what you think about this!

I’m far from being a category theory specialist but I’m still learning and I’m eager to understand/master these concepts! :)