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?
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