Playing with the Either monad in Haskell

After playing with the bitcoin price fetcher, I was disappointed at how… hard it was to deal with the multiple layers of potential errors. I started looking into the errors package on Hackage for a way out. It is a one-stop-shop for all the standard error handling mechanisms in Haskell. It reexports the standard Either and Maybe types, and also adds many helper functions to move between Either and Maybe types, in addition to helping out with the various transformer versions of both (MaybeT and EitherT)

I will play with MaybeT and EitherT later, for now I’m happy to have figured out the Either monad, and want to share the annotated example I’ve cobbled together.

Grab the code into a file, cabal install errors, and start toying with the various places I use the Pass and Error types in the doWork function. You’ll see how nicely Haskell handles a long string of things, where any one of them could fail out.

I’ll have to go rewrite the bitcoin scraper with my newfound knowledge…

import Control.Error

-- A type for my example functions to pass or fail on.
data Flag = Pass | Error

main :: IO ()
main = do
  putStrLn "Starting to do work:"
  let result = doWork
  case result of
    Left  val -> putStrLn $ "Work Result: Failed\n " ++ val
    Right val -> putStrLn $ "Work Result: Passed\n " ++ val
  putStrLn "Ok, finished. Have a nice day"

-- This is a driver function, simulating an error prone path
-- through the app.  Each step could possibly error out, and
-- when any of them do, we want to just bail out.
-- Remember the definition of the Either monad is:
-- instance Monad (Either e) where
--   return = Right
--   Right m >>= k = k m
--   Left e  >>= _ = Left e
-- So a Left value short circuits the rest of the Monad, and a Right value
-- passes the value off to the next step.
doWork :: Either String String
doWork = do -- use do notation syntax sugar for the Either monad

    -- First, do something that may or may not work. We get back a type of
    -- Either String String (since that's the type of the example
    -- eitherFailure function here)
    x <- eitherFailure Pass "Initial Thing"

    -- Based on what we get in x, just go ahead and attempt it.
    -- Note that the function eitherFailure takes a simple
    -- String as its argument.  So we didn't have to unwrap the
    -- first Either value.
    y <- eitherFailure Error ("Second Thing " ++ x)

    -- We can't just wire a Maybe value in the middle here,
    -- since it doesn't typecheck. (Maybe isn't an Either),
    -- even though they play similarly. If we just tried, we'd get:

    -- z <- maybeFailure Error
    -- Couldn't match type `Maybe' with `Either String'

    -- But instead, we can use Control.Error.Util.note to convert
    -- an "empty" Nothing value into a Left value with a descriptive
    -- error.  So now we'd get a proper Either value we can chain
    -- into this overall monad.
    note ("Failed the Maybe: " ++ y) $ maybeFailure Pass y

    -- Since the last line of this `do` block is the type we plan on
    -- returning, there's no `return` call needed.

-- Simple function that we can use to force it to error out with a Left, or
-- pass with a Right value. It just includes some helper text as its content,
-- showing what happened.
eitherFailure :: Flag -> String -> Either String String
eitherFailure Pass  val = Right $ "-> Passed " ++ val
eitherFailure Error val = Left  $ "-> Failed " ++ val

-- Simlar to eitherFailure, but return a (Just String) or a Nothing based on
-- if we told it to fail.
maybeFailure :: Flag -> String -> Maybe String
maybeFailure Pass  val = Just $ "-> Passed maybe " ++ val
maybeFailure Error _   = Nothing

My favorite part of diving into the error library is that my worry from yesterday, except then I’d have to switch the Maybe result out of Lens-Aeson into a “fuller” Either type. is just the note function I demoed above.