George Madrid
George Madrid

Reputation: 703

'Either' propagation with IO()

I am learning Haskell, and having a great time. One of the things I especially enjoy is using the monad error types to propagate error conditions behind the scene in fmap or >>=. For example, in this code, I am using hoauth2 for an authenticated connection. It defines OAuth2Result using Either...

type OAuth2Result a = Either ByteString a  -- from OAuth2

getEntries :: Manager -> AccessToken -> IO(OAuth2Result [Entry])
  -- code omitted

filterResults :: [Entry] -> OAuth2Result [Entry]
filterResults = return $ filter hasUrl 

printEntries :: [Entry] -> IO()   -- What type can I use here?
printEntries Left l  =  -- code omitted
printEntries Right r =  -- code omitted

main = do
    entriesResult <- getEntries mgr token  -- This is an OAuth2Result
    let filtered = entriesResult >>= filterResults

    printEntries filtered

The problem that I am having is when a function has IO like printEntries. In this case, I have to explicitly pattern match to do the error handling. I would sure love to be able to hide it somehow as I did with the filterResults call.

How can I do this?

Thanks!

Upvotes: 0

Views: 193

Answers (1)

ErikR
ErikR

Reputation: 52039

Here's how to do it with runEitherT which requires some lifting and hoisting to get the types right:

import Control.Monad
import Control.Monad.Trans
import Control.Error

import Data.List (isInfixOf)

type Entry = String
type Result a = Either String a

sampleEntries = [ "good 1", "good 2", "good 3", "bad 4", "good 5", "bad 6", "good 7" ]

getEntries :: Int -> IO (Result [Entry])
getEntries n = if n >= 0 && n <= length sampleEntries
                 then return $ Right $ take n sampleEntries
                 else return $ Left $ "invalid n: " ++ show n

filterEntries :: [Entry] -> Result [Entry]
filterEntries ents = if all isGood ents
                       then Right $ ents
                       else Left "found a bad entry"
  where isGood str = isInfixOf "good" str

printEntries :: [Entry] -> IO ()
printEntries ents = forM_ (zip [1..] ents) $ \(i,e) -> print (i,e)

doit n = do
  ents <- (lift $ getEntries n) >>= hoistEither
  filtered <- hoistEither $ filterEntries ents
  lift $ printEntries filtered

main n = do result <- runEitherT $ doit n
            case result of
              Left e -> putStrLn $ "Error: " ++ e
              Right _ -> putStrLn $ "no errors"

Note the following behavior:

  • main 100 fails because getEntries returns an error
  • main 4 fails because filterEntries returns an error
  • main 3 succeeds

Upvotes: 1

Related Questions