{- |

When 'IO' is the monad of a monadic fold, each step in the evaluation of the
fold might throw an exception, thus causing the entire evaluation to fail. This
module provides some functions for capturing the results from /part of/ a fold
even if some of the steps did not succeed.

This module uses 'try' from the @safe-exceptions@ package and will not catch
async exceptions.

-}

module Control.Foldl.Exceptions
  (
  -- * Stopping at the first exception
    exHalt_, exHalt

  -- * Continuing past failed steps
  , exSkip_, exSkip

  ) where

-- foldl
import Control.Foldl

-- safe-exceptions
import Control.Exception.Safe (Exception, MonadCatch, SomeException, try)

{- |

Performs the steps of a fold up until the first step that throws an exception,
then produces the result obtained thus far.

==== Example

>>> import Control.Exception
>>> f x = if x < 10 then return x else throw Overflow
>>> xs = [1, 2, 500, 4] :: [Integer]

Since @f 500@ produces an exception, the following fold fails:

>>> fold1 = premapM f (generalize list)
>>> foldM fold1 xs
*** Exception: arithmetic overflow

By applying 'untilFirstException', we can produce a new fold that returns
the intermediate result at the point where the exception occurs.

>>> fold2 = exHalt_ fold1
>>> foldM fold2 xs
[1,2]

-}

exHalt_ :: forall m a b.
    (Monad m, MonadCatch m) => FoldM m a b -> FoldM m a b

exHalt_ :: forall (m :: * -> *) a b.
(Monad m, MonadCatch m) =>
FoldM m a b -> FoldM m a b
exHalt_ FoldM m a b
f = forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap forall a b. (a, b) -> b
snd (forall e (m :: * -> *) a b.
(Exception e, Monad m, MonadCatch m) =>
FoldM m a b -> FoldM m a (Maybe e, b)
exHalt @SomeException FoldM m a b
f)

{- |

Performs the steps of a fold up until the first step that throws an exception,
then produces a tuple containing:

  1. The exception that was thrown, if any.
  2. The result obtained thus far.

The first type parameter lets you specify what type of exception to catch. Any
other type of exception will still cause the entire fold's evaluation to fail.

==== Example

>>> import Control.Exception
>>> f x = if x < 10 then return x else throw Overflow
>>> xs = [1, 2, 500, 4] :: [Integer]
>>> fold1 = premapM f (generalize list)

>>> :set -XTypeApplications
>>> fold2 = exHalt @ArithException fold1

>>> foldM fold2 xs
(Just arithmetic overflow,[1,2])

-}

exHalt :: forall e m a b.
    (Exception e, Monad m, MonadCatch m) =>
    FoldM m a b -> FoldM m a (Maybe e, b)

exHalt :: forall e (m :: * -> *) a b.
(Exception e, Monad m, MonadCatch m) =>
FoldM m a b -> FoldM m a (Maybe e, b)
exHalt (FoldM x -> a -> m x
step m x
begin x -> m b
done) = forall (m :: * -> *) a b x.
(x -> a -> m x) -> m x -> (x -> m b) -> FoldM m a b
FoldM forall {a}. Exception a => (Maybe a, x) -> a -> m (Maybe a, x)
step' forall {a}. m (Maybe a, x)
begin' forall {a}. (a, x) -> m (a, b)
done'
  where
    begin' :: m (Maybe a, x)
begin' =
      do
        x
y <- m x
begin
        forall (m :: * -> *) a. Monad m => a -> m a
return (forall a. Maybe a
Nothing, x
y)

    step' :: (Maybe a, x) -> a -> m (Maybe a, x)
step' x' :: (Maybe a, x)
x'@(Just a
_, x
_) a
_ = forall (m :: * -> *) a. Monad m => a -> m a
return (Maybe a, x)
x'
    step' (Maybe a
Nothing, x
x1) a
a =
      do
        Either a x
x2Either <- forall (m :: * -> *) e a.
(MonadCatch m, Exception e) =>
m a -> m (Either e a)
try (x -> a -> m x
step x
x1 a
a)
        case Either a x
x2Either of
            Left a
e   -> forall (m :: * -> *) a. Monad m => a -> m a
return (forall a. a -> Maybe a
Just a
e, x
x1)
            Right x
x2 -> forall (m :: * -> *) a. Monad m => a -> m a
return (forall a. Maybe a
Nothing, x
x2)

    done' :: (a, x) -> m (a, b)
done' (a
eMaybe, x
x) =
      do
        b
b <- x -> m b
done x
x
        forall (m :: * -> *) a. Monad m => a -> m a
return (a
eMaybe, b
b)

{- |

Perform only steps of a fold that succeed, ignoring any steps that fail.

==== Example

>>> import Control.Exception
>>> f x = if x < 10 then return x else throw Overflow
>>> xs = [1, 2, 500, 4] :: [Integer]

Since @f 500@ produces an exception, the following fold fails:

>>> fold1 = premapM f (generalize list)
>>> foldM fold1 xs
*** Exception: arithmetic overflow

By applying 'exSkip_', we can produce a new fold that produces a result from all
steps that /don't/ fail:

>>> fold2 = exSkip_ fold1
>>> foldM fold2 xs
[1,2,4]

-}

exSkip_ :: forall m a b.
    (Monad m, MonadCatch m) => FoldM m a b -> FoldM m a b

exSkip_ :: forall (m :: * -> *) a b.
(Monad m, MonadCatch m) =>
FoldM m a b -> FoldM m a b
exSkip_ FoldM m a b
f = forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap forall a b. (a, b) -> b
snd (forall e (m :: * -> *) a b.
(Exception e, Monad m, MonadCatch m) =>
FoldM m a b -> FoldM m a ([e], b)
exSkip @SomeException FoldM m a b
f)

{- |

Perform only steps of a fold that succeed, collecting the exceptions thrown from
each step that fail. Produces a tuple containing:

  1. A list of any exceptions thrown.
  2. The result obtained from the steps that succeeded.

The first type parameter lets you specify what type of exception to catch. Any
other type of exception will still cause the entire fold's evaluation to fail.

==== Example

>>> import Control.Exception
>>> f x = if x < 10 then return x else throw Overflow
>>> xs = [1, 2, 500, 4] :: [Integer]

Since @f 500@ produces an exception, the following fold fails:

>>> fold1 = premapM f (generalize list)
>>> foldM fold1 xs
*** Exception: arithmetic overflow

By applying 'exSkip', we can produce a new fold that produces a result from all
steps that /don't/ fail:

>>> :set -XTypeApplications
>>> fold2 = exSkip @ArithException fold1
>>> foldM fold2 xs
([arithmetic overflow],[1,2,4])

-}

exSkip :: forall e m a b.
    (Exception e, Monad m, MonadCatch m) =>
    FoldM m a b -> FoldM m a ([e], b)

exSkip :: forall e (m :: * -> *) a b.
(Exception e, Monad m, MonadCatch m) =>
FoldM m a b -> FoldM m a ([e], b)
exSkip (FoldM x -> a -> m x
step m x
begin x -> m b
done) = forall (m :: * -> *) a b x.
(x -> a -> m x) -> m x -> (x -> m b) -> FoldM m a b
FoldM forall {a} {c}.
Exception a =>
([a] -> c, x) -> a -> m ([a] -> c, x)
step' forall {a}. m (a -> a, x)
begin' forall {a} {a}. ([a] -> a, x) -> m (a, b)
done'
  where
    begin' :: m (a -> a, x)
begin' =
      do
        x
y <- m x
begin
        forall (m :: * -> *) a. Monad m => a -> m a
return (forall a. a -> a
id, x
y)

    step' :: ([a] -> c, x) -> a -> m ([a] -> c, x)
step' ([a] -> c
es, x
x1) a
a =
      do
        Either a x
x2Either <- forall (m :: * -> *) e a.
(MonadCatch m, Exception e) =>
m a -> m (Either e a)
try (x -> a -> m x
step x
x1 a
a)
        case Either a x
x2Either of
            Left a
e   -> forall (m :: * -> *) a. Monad m => a -> m a
return ([a] -> c
es forall b c a. (b -> c) -> (a -> b) -> a -> c
. (a
e forall a. a -> [a] -> [a]
:), x
x1)
            Right x
x2 -> forall (m :: * -> *) a. Monad m => a -> m a
return ([a] -> c
es, x
x2)

    done' :: ([a] -> a, x) -> m (a, b)
done' ([a] -> a
es, x
x) =
      do
        b
b <- x -> m b
done x
x
        forall (m :: * -> *) a. Monad m => a -> m a
return ([a] -> a
es [], b
b)