I'm trying to cook up a simple example using IO and Maybe monads. The program reads a node from the DOM and writes some innerHTML to it.
What I'm hung up on is the combination of IO and Maybe, e.g. IO (Maybe NodeList).
How do I short circuit or throw an error with this setup?
I could use getOrElse to extract a value or set a default value, but setting the default value to just an empty array doesn't help anything.
import R from 'ramda';
import { IO, Maybe } from 'ramda-fantasy';
const Just = Maybe.Just;
const Nothing = Maybe.Nothing;
// $ :: String -> Maybe NodeList
const $ = (selector) => {
const res = document.querySelectorAll(selector);
return res.length ? Just(res) : Nothing();
}
// getOrElse :: Monad m => m a -> a -> m a
var getOrElse = R.curry(function(val, m) {
return m.getOrElse(val);
});
// read :: String -> IO (Maybe NodeList)
const read = selector =>
IO(() => $(selector));
// write :: String -> DOMNode -> IO
const write = text =>
(domNode) =>
IO(() => domNode.innerHTML = text);
const prog = read('#app')
// What goes here? How do I short circuit or error?
.map(R.head)
.chain(write('Hello world'));
prog.runIO();
You could try writing an
EitherIOmonad transformer. Monad transformers allow you to combine the effects of two monads into a single monad. They can be written in a generic way such that we can create dynamic combinations of monads as needed, but here I'm just going to demonstrate a static coupling ofEitherandIO.First we need a way to go from
IO (Either e a)toEitherIO e aand a way to go fromEitherIO e atoIO (Either e a)And we'll need a couple helper functions for taking other flat types to our nested monad
To conform with fantasy land, our new
EitherIOmonad has achainmethod andoffunction and obeys the monad laws. For your convenience, I also implemented the functor interface with themapmethod.EitherIO.js
Adapting your program to use EitherIO
What's nice about this is your
readandwritefunctions are fine as they are - nothing in your program needs to change except for how we structure the calls inprogAdditional explanation
Checking for errors
Go ahead and change
'#app'to some non-matching selector (eg)'#foo'. Re-run the program and you'll see the appropriate error barfed into the consoleRunnable demo
You made it this far. Here's a runnable demo as your reward: https://www.webpackbin.com/bins/-Kh5NqerKrROGRiRkkoA
Generic transform using EitherT
A monad transformer takes a monad as an argument and creates a new monad. In this case,
EitherTwill take some monadMand create a monad that effectively behaves hasM (Either e a).So now we have some way to create new monads
And again we have functions to lifting the flat types into our nested type
Lastly a custom run function that makes it easier to handle our nested
IO (Either e a)type - notice, one layer of abstraction (IO) is removed so we only have to think about theEitherEitherT
is the bread and butter - the primary difference you see here is that
EitherTaccepts a monadMas an input and creates/returns a new Monad typeEitherIO
can now be implemented in terms of
EitherT– a dramatically simplified implementationUpdates to our program
Runnable demo using EitherT
Here's the runnable code using EitherT: https://www.webpackbin.com/bins/-Kh8S2NZ8ufBStUSK1EU