Привет, Рубенс, классная статья! Функциональное программирование и теория категорий в JavaScript - это так весело!

Кстати, трюки Symbol.species очень крутые, я оставлю их на потом.

Однако я не уверен, правильно ли я понял ваше объяснение о монадах (цитата мистера Элиотта о характеристиках монады мне не очень понятна).

Я понимаю, что монада - это функтор (у нее есть метод map) с функцией flatMap и функцией of, которые позволяют вам обернуть значение монадой.

Что меня беспокоит в вашей монаде Zord, так это то, что функция flatMap может развернуть / сгладить столько уровней, сколько необходимо, чтобы получить доступ к обернутому примитивному значению.

Из того, что я прочитал здесь (http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html) и здесь (https://en.wikibooks.org/wiki/Haskell/ Category_theory # Monads ), я не думаю, что ваш метод Zord flatMap уважает сигнатуру монады flatMap, то есть:

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

flatMap ожидает функцию (a -> Monad b), которая принимает значение (значение, заключенное в монаду) и возвращает значение, заключенное в указанную монаду.

В случае с Зордом я был бы:

// 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'

Но если у нас будет больше уровней вложенности:

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'))

Здесь у нас сложилось впечатление, что структура данных сглажена на одном уровне, но такое поведение вызвано фактическим определением flatMap. Этот случай явно является случаем, когда мы передаем функцию идентичности x => x в flatMap.

Фактически, согласно определению монады здесь, flatMap(x => x) эквивалентен функции join, которая выравнивает один уровень вложенности, т.е.

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')

Если я повторно воспользуюсь фрагментом кода из вашей статьи, думаю, мы должны получить:

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

В случае Promises есть исключение, потому что Promise.resolve не ведет себя как Monad.of функция определения монады, она автоматически сглаживает обещания :) и .then объединяет поведение .map и .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'))

Поэтому я думаю, что Promise можно назвать «особой» монадой в 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

Фух! Что за стена текста ...

Скажи мне, что ты думаешь об этом!

Я далек от того, чтобы быть специалистом по теории категорий, но я все еще учусь и очень хочу понять / освоить эти концепции! :)