{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE FlexibleContexts #-}
-- | Effects which fail.
module Control.Eff.Fail( Fail
                       , die
                       , runFail
                       , ignoreFail
                       , onFail
                       ) where

import Data.Typeable
import Control.Eff
import Control.Monad

-- | 'Fail' represents effects which can fail. This is akin to the Maybe monad.
data Fail v = Fail
  deriving (Functor, Typeable)

-- | Makes an effect fail, preventing future effects from happening.
die :: Member Fail r
    => Eff r ()
die = send (const (inj Fail))
{-# INLINE die #-}

-- | Runs a failable effect, such that failed computation return 'Nothing', and
--   'Just' the return value on success.
runFail :: Eff (Fail :> r) a
        -> Eff r (Maybe a)
runFail m = loop (admin m)
 where
  loop (Val x) = return (Just x)
  loop (E u)   = handleRelay u loop (const (return Nothing))
{-# INLINE runFail #-}

-- | Given a computation to run on failure, and a computation that can fail,
--   this function runs the computation that can fail, and if it fails, gets
--   the return value from the other computation. This hides the fact that a
--   failure even happened, and returns a default value for when it does.
onFail :: Eff r a           -- ^ The computation to run on failure.
       -> Eff (Fail :> r) a -- ^ The computation which can fail.
       -> Eff r a
onFail sideshow mainEvent = do
  r <- runFail mainEvent
  case r of
    Nothing -> sideshow
    Just y  -> return y
{-# INLINE onFail #-}

-- | Ignores a failure event. Since the event can fail, you cannot inspect its
--   return type, because it has none on failure. To inspect it, use 'runFail'.
ignoreFail :: Eff (Fail :> r) a
           -> Eff r ()
ignoreFail = onFail (return ()) . void
{-# INLINE ignoreFail #-}