monadsjs
Installation
npm install --save monadsjs
Monads
Don't forget to check tests.
Identity
It's the most basic monad implementation which only wraps a value. It doesn't make much sense to use this monad as is. Understanding Identity monad is quite crucial to move forward. It can be a starting point for any new monad.
// Identity.unit :: a -> M a// Identity.bind :: (a -> M b) -> M b// Identity.map :: (a -> b) -> M b Identity ; // Identity(3)
Maybe (Just | Nothing)
Maybe monad represents a value (Just
) or monadic zero (Nothing
). It's very convinient way to
literally, let your code fail. Instead of writing multiple checks and try-catches just check type of
the value at the end of transformations chain. In this particular implementation Maybe is just a
union type which exists only if you are using TypeScript.
Just wraps actual value.
// Just.unit :: a -> Just a// Just.bind :: (a -> Just b | Nothing) -> Just b | Nothing// Just.map :: (a -> b) -> Just b | Nothing Just ; // Just(3)
At that point it's really similar to Identity monad. Things looks different when Nothing comes in.
Nothing can be explicity returned from function passed to bind or when error occures. Further
execution is stopped. In the following example double
is not going to be called due to TypeError
throwed in firstWordLength
when empty array is passed.
// Nothing.unit :: () -> Nothing// Nothing.bind :: (a -> Just b | Nothing) -> Nothing// Nothing.map :: (a -> b) -> Nothing const firstWordLength = words0length;const double = n * 2; Just ; ; // Nothing()
Either (Left | Right)
Either monad is almost like a Maybe monad. Right
and Just
are basically the same. Either unlike
Maybe doesn't have monadic zero. Left
is supposed to wrap Error
. Left
can be explicity
returned from function passed to bind or when error occures. In this particular implementation
Either is just a union type which exists only if you are using TypeScript. In the following
example double
is not going to be called due to TypeError
throwed in firstWordLength
when empty array is passed.
// Right.unit :: a -> Right a// Right.bind :: (a -> Right b | Left b) -> Right b | Left b// Right.map :: (a -> b) -> Right b | Left b Right ; // Right(3)
// Left.unit :: a -> Left a// Left.bind :: (a -> Right b | Left b) -> Left a// Left.map :: (a -> b) -> Left a const firstWordLength = words0length;const double = n * 2; Right ; // Left(TypeError: Cannot read property 'length' of undefined)
IO
IO monad was invented to make it possible to perform side effects in pure functional languages
(here I have Haskell in mind). In JavaScript we can take an advantage of IO monads to change unpure
functions into pure which makes them easy to test. This implementation is also
a Setoid. IO.equals
makes it
easy to deeply compare two IO monads.
// IO.unit :: (a, ...) -> IO a, [...]// IO.bind :: ((a, ...) -> IO) -> IO a, [...]// IO.run :: () -> void { return IO;} const m = ; // IO(sayHello, ["World"]) ; // passes m; // alerts: Hello World!
Continuation (Promise)
Promise out of the box is a great Continuation monad implementation! Promise.resolve
corresponds
to unit
and Promise.then
corresponds to bind
. Provided Continuation implementation is a minimal
wrapper for Promise with methods you know from other monads (bind
and unit
). Don't use it, use
Promise.
// Continuation.unit :: a -> Continuation a// Continuation.bind :: (a -> b) -> Continuation b Continuation ; // Continuation(<Promise>{ "login": "octocat", "id": ... })
List
List monad is a clever one. List constructor accepts Iterable
and allows for lazy transformations.
It is achieved using generators. What is more, List implements Symbol.iterator
so you can iterate
through it like any regular iterator.
// List.unit :: a -> List a// List.bind :: (a -> List b) -> List b// List.map :: (a -> b) -> List b// List.forEach :: (a -> void) -> void const lazySpy = sinon;const nextLazySpy = sinon; const m = List ; // List([object Generator]) const asIterable = mSymboliterator; // no computation was done so far ;; ; // only first value was computed ;; ;;; ;;;
Monads next
Monad next is a bit different approach to implementing monads in JavaScript. It requires ::
bind
operator wich is not yet supported in TypeScript (shame on you TypeScript, ave Babel!) and having
types when implemening monads is more important for me at this very moment.
const List = * { iterable; } * { for let x of this ; }; const m = List::List::List;