{-# LANGUAGE RankNTypes, TypeFamilies, FlexibleContexts, ScopedTypeVariables, MultiParamTypeClasses #-}

{-# LANGUAGE FlexibleInstances, DataKinds, GADTs #-}

{-# LANGUAGE NoMonomorphismRestriction #-}

-- | A neat effect that you can use to get early returns in your functions. Here's how to use it.

--

--   Before:

--

-- @

--   f = do

--       m1 <- maybeFunc1

--       case m1 of

--           Nothing -> return "1 nothing"

--           Just x -> do

--               m2 <- maybeFunc2

--               case m2 of

--                   Nothing -> return "2 nothing"

--                   Just y -> return (x <> y)

-- @

--

--   After:

--

-- @

--   f = handleEarly $ do

--       m1 <- maybeFunc1

--       x <- ifNothingEarlyReturn "1 nothing" m1

--       m2 <- maybeFunc2

--       y <- ifNothingEarlyReturn "2 nothing" m2

--       return (x <> y)

-- @

--

--   You can use the 'earlyReturn' function directly, or one of the helpers for common use cases.

module Control.Effects.Early

    ( module Control.Effects, Early(..)

    , earlyReturn, handleEarly, onlyDo, ifNothingEarlyReturn, ifNothingDo

    , ifLeftEarlyReturn, ifLeftDo ) where



import Import



import Control.Effects



newtype EarlyValue a = EarlyValue { getEarlyValue :: a }

newtype Early a m = EarlyMethods

    { _earlyReturn :: forall b. a -> m b }

instance Effect (Early a) where

    liftThrough (EarlyMethods f) = EarlyMethods (lift . f)

    mergeContext m = EarlyMethods (\a -> do

        f <- _earlyReturn <$> m

        f a)



instance (Monad m, a ~ b) => MonadEffect (Early a) (ExceptT (EarlyValue b) m) where

    effect = EarlyMethods (throwE . EarlyValue)



-- | Allows you to return early from a function. Make sure you 'handleEarly' to get the actual

--   result out.

earlyReturn :: forall a b m. MonadEffect (Early a) m => a -> m b

EarlyMethods earlyReturn = effect



-- | Get the result from a computation. Either the early returned one, or the regular result.

handleEarly :: Monad m => ExceptT (EarlyValue a) m a -> m a

handleEarly = fmap (either getEarlyValue id)

            . runExceptT



-- | Only do the given action and exit early with it's result.

onlyDo :: MonadEffect (Early a) m => m a -> m b

onlyDo m = m >>= earlyReturn



-- | Early return the given value if the 'Maybe' is 'Nothing'. Otherwise, contnue with the value

--   inside of it.

ifNothingEarlyReturn :: MonadEffect (Early a) m => a -> Maybe b -> m b

ifNothingEarlyReturn a = maybe (earlyReturn a) return



-- | Only do the given action and early return with it's result if the given value is 'Nothing'.

--   Otherwise continue with the value inside of the 'Maybe'.

ifNothingDo :: MonadEffect (Early a) m => m a -> Maybe b -> m b

ifNothingDo m = maybe (onlyDo m) return



-- | If the value is a 'Left', get the value, process it and early return the result.

--   Otherwise just return the 'Right' value.

ifLeftEarlyReturn :: MonadEffect (Early c) m => (a -> c) -> Either a b -> m b

ifLeftEarlyReturn f = either (earlyReturn . f) return



-- | If the value is a 'Left', get the value, process it and only do the resulting action.

--   Otherwise just return the 'Right' value.

ifLeftDo :: MonadEffect (Early c) m => (a -> m c) -> Either a b -> m b

ifLeftDo f = either (onlyDo . f) return