How can I convert this function from ExceptT to Except in Haskell?

Viewed 238

I got very confused with Except, specially since there is no good tutorial around the web. I don't know how can I convert this function from ExceptT to Except:

data Error = Empty deriving (Show)

badFunction :: ExceptT Error IO ()
badFunction = throwError Empty

main :: IO ()
main = do
    caught_result <- runExceptT badFunction
    case caught_result of
      Left _ -> putStrLn "caught some error"
      Right _ -> putStrLn "no errors were caught"
2 Answers

The reason badFunction should be an ExceptT Error IO () is because runExceptT has type runExceptT :: ExceptT e m a -> m (Either e a). Since your main has type main :: IO (), this means that runExceptT badFunction needs to be IO …, hence the m in the ExceptT e m a should be IO.

But you do not per se need this, your badFunction does not do any IO, so you can define it as an Except:

badFunction :: Except Error ()
badFunction = throwE Empty

Then you can use it in an IO () by using runIdentity to obtain the value out of the identity, and then use pure to "wrap" the result in an IO:

main :: IO ()
main = do
    caught_result <- pure (runIdentity (runExceptT badFunction))
    case caught_result of
      Left _ -> putStrLn "caught some error"
      Right _ -> putStrLn "no errors were caught"

But then we can just use a let … clause in the main and remove the pure:

main :: IO ()
main = do
    let caught_result = runIdentity (runExceptT badFunction)
    case caught_result of
      Left _ -> putStrLn "caught some error"
      Right _ -> putStrLn "no errors were caught"

As @JonPurdy says, the combination of runIdentity and runExceptT is runExcept :: Except e a -> Either e a, so we can rewrite this to:

main :: IO ()
main = do
    let caught_result = runExcept badFunction
    case caught_result of
      Left _ -> putStrLn "caught some error"
      Right _ -> putStrLn "no errors were caught"

You write

badFunction :: ExceptT Error IO ()
badFunction = throwError Empty

which suggests that it does IO. But it doesn't do any IO. As Willem Van Onsem points out, one option is to indicate that by using Except instead:

badFunction :: Except Error ()

By the definition of Except, this is identical to

badFunction :: ExceptT Error Identity ()

There is a third option:

badFunction :: Monad m => ExceptT Error m ()

This gives you full flexibility!

runExcept badFunction :: Either Error ()
runExceptT badFunction :: Monad m => m (Either Error ())

So if you choose, you can write

main :: IO ()
main = do
    caught_result <- runExceptT badFunction
    case caught_result of
      Left _ -> putStrLn "caught some error"
      Right _ -> putStrLn "no errors were caught"

Whatever you think is clearer.

Related