{-# LANGUAGE AllowAmbiguousTypes #-}

-- This Source Code Form is subject to the terms of the Mozilla Public
-- License, v. 2.0. If a copy of the MPL was not distributed with this
-- file, You can obtain one at https://mozilla.org/MPL/2.0/.

{- |
Copyright   :  (c) 2023 Sayo Koyoneda
License     :  MPL-2.0 (see the file LICENSE)
Maintainer  :  ymdfield@outlook.jp

An effect to escape from the normal control structure with an exception value in the middle of a context.
-}
module Data.Effect.Except where

-- | An effect to escape from the normal control structure with an exception value of type @e@ in the middle of a context.
data Throw e (a :: Type) where
    -- | Throws an exception; that is, escapes from the normal control structure with an exception value in the middle of a context.
    Throw :: e -> Throw e a

-- | An effect to catch exceptions.
data Catch e f (a :: Type) where
    -- | Catches exceptions within a scope and processes them according to the given exception handler.
    Catch
        :: f a
        -- ^ The scope in which to catch exceptions.
        -> (e -> f a)
        -- ^ Exception handler. Defines the processing to perform when an exception is thrown within the scope.
        -> Catch e f a

makeEffect [''Throw] [''Catch]

-- | Throws the given `Either` value as an exception if it is `Left`.
liftEither :: (Throw e <: f, Applicative f) => Either e a -> f a
liftEither :: forall e (f :: * -> *) a.
(Throw e <: f, Applicative f) =>
Either e a -> f a
liftEither = (e -> f a) -> (a -> f a) -> Either e a -> f a
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either e -> f a
forall e a (f :: * -> *). SendFOE (Throw e) f => e -> f a
throw a -> f a
forall a. a -> f a
forall (f :: * -> *) a. Applicative f => a -> f a
pure
{-# INLINE liftEither #-}

-- | Throws the result of the given action as an exception if it is `Left`.
joinEither :: (Throw e <: m, Monad m) => m (Either e a) -> m a
joinEither :: forall e (m :: * -> *) a.
(Throw e <: m, Monad m) =>
m (Either e a) -> m a
joinEither = (m (Either e a) -> (Either e a -> m a) -> m a
forall a b. m a -> (a -> m b) -> m b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= (e -> m a) -> (a -> m a) -> Either e a -> m a
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either e -> m a
forall e a (f :: * -> *). SendFOE (Throw e) f => e -> f a
throw a -> m a
forall a. a -> m a
forall (f :: * -> *) a. Applicative f => a -> f a
pure)
{-# INLINE joinEither #-}

-- | If the given `Either` value is `Left`, execute it as an action.
joinExcept :: (Monad m) => Either (m a) a -> m a
joinExcept :: forall (m :: * -> *) a. Monad m => Either (m a) a -> m a
joinExcept = (m a -> m a) -> (a -> m a) -> Either (m a) a -> m a
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either m a -> m a
forall a. a -> a
id a -> m a
forall a. a -> m a
forall (f :: * -> *) a. Applicative f => a -> f a
pure
{-# INLINE joinExcept #-}

-- | If the result of the given action is `Left`, execute it as an action.
exc :: (Monad m) => m (Either (m a) a) -> m a
exc :: forall (m :: * -> *) a. Monad m => m (Either (m a) a) -> m a
exc = (m (Either (m a) a) -> (Either (m a) a -> m a) -> m a
forall a b. m a -> (a -> m b) -> m b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= (m a -> m a) -> (a -> m a) -> Either (m a) a -> m a
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either m a -> m a
forall a. a -> a
id a -> m a
forall a. a -> m a
forall (f :: * -> *) a. Applicative f => a -> f a
pure)
{-# INLINE exc #-}

-- | If an exception occurs, executes the given exception handler, but the exception is not stopped there and is rethrown.
withExcept
    :: (Catch e <<: f, Throw e <: f, Applicative f)
    => f a
    -- ^ Scope to which the exception handler applies
    -> (e -> f ())
    -- ^ Exception handler
    -> f a
withExcept :: forall e (f :: * -> *) a.
(Catch e <<: f, Throw e <: f, Applicative f) =>
f a -> (e -> f ()) -> f a
withExcept f a
thing e -> f ()
after = f a
thing f a -> (e -> f a) -> f a
forall a e (f :: * -> *).
SendHOE (Catch e) f =>
f a -> (e -> f a) -> f a
`catch` \e
e -> e -> f ()
after e
e f () -> f a -> f a
forall a b. f a -> f b -> f b
forall (f :: * -> *) a b. Applicative f => f a -> f b -> f b
*> e -> f a
forall e a (f :: * -> *). SendFOE (Throw e) f => e -> f a
throw e
e
{-# INLINE withExcept #-}

-- | If an exception occurs, executes the specified action, but the exception is not stopped there and is rethrown.
onExcept
    :: forall e f a
     . (Catch e <<: f, Throw e <: f, Applicative f)
    => f a
    -- ^ Scope in which to detect exceptions
    -> f ()
    -- ^ Action to execute in case of an exception
    -> f a
onExcept :: forall e (f :: * -> *) a.
(Catch e <<: f, Throw e <: f, Applicative f) =>
f a -> f () -> f a
onExcept f a
thing f ()
after = f a
thing f a -> (e -> f ()) -> f a
forall e (f :: * -> *) a.
(Catch e <<: f, Throw e <: f, Applicative f) =>
f a -> (e -> f ()) -> f a
`withExcept` \(e
_ :: e) -> f ()
after
{-# INLINE onExcept #-}