I am looking for the most common way to do something like:
x :: IO ((),[SomeException])
x = do
void y
void z
aggregating exceptions that may be thrown by y
and z
and returning them as part of the type of x
.
Is there a well-known monad / tutorial for this?
So the important question here is augustss
- "If y throws an exception, what would the value of a
be?"
If you have default values for a
, and b
, you can use try
to catch your exceptions and aggregate them using WriterT
:
x :: IO (C, [SomeException])
x = runWriterT $ do
a <- rescue defaultA y
b <- rescue defaultB z
return $ f a b
rescue :: a -> IO a -> WriterT [SomeException] IO a
rescue a m = do
res <- lift $ try m
case res of
Left e -> do
tell [e]
return a
Right a' -> return a'
data A
data B
data C
y :: IO A
y = undefined
defaultA :: A
defaultA = undefined
z :: IO B
z = undefined
defaultB :: B
defaultB = undefined
f :: A -> B -> C
f = undefined
Without defaults though, you can't rescue exceptions and continue computation.
If y
throws an exception, then you will never reach z
. Likewise, if z
throws an exception, that means that y
has not thrown an exception. So the typical way to track exceptions is just tracking one -- the one that was thrown -- with ErrorT
.
x :: ErrorT SomeException IO Foo
x = do
a <- y
b <- z
return $ f a b
useX :: IO Quux
useX = do
errOrVal <- runErrorT x
case errOrVal of
Left err -> logError err >> quux1
Right v -> quux2 v
Here are the type assumptions I used:
{-# LANGUAGE EmptyDataDecls #-}
import Control.Monad.Error
data Foo; data Bar; data Baz; data Quux; data SomeException
instance Error SomeException
y :: ErrorT SomeException IO Bar; y = undefined
z :: ErrorT SomeException IO Baz; z = undefined
f :: Bar -> Baz -> Foo; f = undefined
quux1 :: IO Quux; quux1 = undefined
quux2 :: Foo -> IO Quux; quux2 = undefined
logError :: SomeException -> IO (); logError = undefined
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With