Привет, Рубенс, классная статья! Функциональное программирование и теория категорий в 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
Фух! Что за стена текста ...
Скажи мне, что ты думаешь об этом!
Я далек от того, чтобы быть специалистом по теории категорий, но я все еще учусь и очень хочу понять / освоить эти концепции! :)