Search code examples
haskellexceptionlazy-evaluationmonad-transformers

Mapping from Exception to ExceptT


I am reading a chapter about exceptions so I decided to experiment and map an Exception to the ExceptT monad transformer. My function getInt can read an integer from a console and report exceptions as Left "what's wrong":

getInt :: ExceptT String IO Int
getInt = ExceptT $ do
    Right . read <$> getLine
    `catch` \(e :: ErrorCall) -> return . Left . show $ e

I tried it, unfortunately the exception was not caught, laziness is mocking me. OK, let me make it strict with seq.

getInt :: ExceptT String IO Int
getInt = ExceptT $ do
    read <$> getLine >>= seq <*> return . Right
    `catch` \(e :: ErrorCall) -> return . Left . show $ e

The result is: Left "Prelude.read: no parse". Now it works!

My question here is - is this the right way how to map an Exception to the ExceptT?


Solution

  • The correct way is indeed to force the evaluation of read. This can be done more elegantly with the call-by-value application $!

    getInt :: ExceptT SomeException IO Int
    getInt = ExceptT $ liftIO $ try @SomeException $ do
      i <- getLine
      pure $! read i
    

    It is usually better to parameterize the underlying monad (for testability and reusability reasons), so we could write

    getInt :: (MonadError SomeException m, MonadIO m) => m Int
    getInt = liftEither =<< liftIO $ try @SomeException $ do
      i <- getLine
      pure $! read i
    

    insdead of a concrete monad transformer stack ExceptT SomeException IO Int