What kind of monad should Haskell use to aggregate exceptions that might occur when executing a sequence of expressions?

I am looking for the most common way to do something like:

x :: IO ((),[SomeException]) x = do void y void z 

aggregating exceptions that y and z can throw and return them as part of type x .

Is there a famous monad / textbook for this?

+4
source share
3 answers

So the important question here is: augustss - "If y throws an exception, what will the value of a ?

If you have default values ​​for a and b , you can use try to catch your exceptions and fill them with WriterT :

 x :: IO (C, [SomeException]) x = runWriterT $ do a <- rescue defaultA yb <- rescue defaultB z return $ fab rescue :: a -> IO a -> WriterT [SomeException] IO a rescue am = 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 the default, you cannot save the exceptions and continue the calculation.

+4
source

If y throws an exception, you will never reach z . Similarly, if z throws an exception, it means that y does not throw an exception. So the typical way to track exceptions is to simply track one - the one that was thrown - with ErrorT .

 x :: ErrorT SomeException IO Foo x = do a <- y b <- z return $ fab 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 
+4
source

The important thing about exceptions is that if the action of IO a raises an exception, you will not get any value of a . Since the binding operator (>>=) :: Monad m => ma -> (a -> mb) -> mb from monads allows later actions to depend on the results of the previous ones, this means that we will not be able to perform the following actions after how do you fail.

If you have default values, you can use a ramp approach . However, from your example, it seems that you only care about the sequence of independent actions using the operator (>>) . You can make a monoid from this, but I think the simplest approach is to have a function that executes an IO () list of actions and collects any exceptions from the list:

 import Control.Exception (SomeException, try) import Data.Either (lefts) exceptions :: [IO ()] -> IO [SomeException] exceptions = fmap lefts . mapM try 

You will need to use an action list instead of a do notation, though:

 > :{ | exceptions [ putStrLn "foo" | , throwIO DivideByZero | , putStrLn "bar" | , throwIO (IndexOutOfBounds "xyzzy") | ] | :} foo bar [divide by zero,array index out of range: xyzzy] 
+4
source

Source: https://habr.com/ru/post/1399622/


All Articles