{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE UndecidableInstances #-}

module Control.Effect.Exception
       (EffException, try, throw, liftEither, effToExceptT, exceptTToEff) where

import Control.Effect
import Control.Monad ((>=>))
import Control.Monad.Trans.Class (MonadTrans(..))
import Control.Monad.Trans.Except

class Monad m => EffException e m | m -> e where
  liftEither :: Either e a -> m a

instance Monad m => EffException e (Eff (Either e) m) where
  liftEither = interpret
  {-# INLINE liftEither #-}

instance {-# OVERLAPPABLE #-} (EffException e m) => EffException e (Eff f m) where
  liftEither = lift . liftEither
  {-# INLINE liftEither #-}

throw :: EffException e m => e -> m a
throw = liftEither . Left
{-# INLINE throw #-}

try :: Monad m => Eff (Either e) m a -> m (Either e a)
try = runExceptT . effToExceptT
{-# INLINE try #-}

effToExceptT :: Monad m => Eff (Either e) m a -> ExceptT e m a
effToExceptT = translate (lift . ExceptT . return)
{-# INLINE effToExceptT #-}

exceptTToEff :: (Monad m, EffException e m) => ExceptT e m a -> m a
exceptTToEff = runExceptT >=> liftEither
{-# INLINE exceptTToEff#-}