{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}

#ifdef MTL
{-# OPTIONS_GHC -fno-warn-orphans #-}
#endif

module Control.Effect.Lift (
    EffectLift, Lift (..), runLift, lift, liftEffect
) where

import Control.Monad.Effect

#ifdef MTL
import Control.Monad.Trans (MonadIO (..))

instance EffectLift IO l => MonadIO (Effect l) where
    liftIO = lift
#endif

-- | An effect described by a monad.
newtype Lift m a = Lift { unLift :: m a }

type instance Is Lift f = IsLift f

type family IsLift f where
    IsLift (Lift m) = 'True
    IsLift f = 'False

class (Monad m, MemberEffect Lift (Lift m) l) => EffectLift m l
instance (Monad m, MemberEffect Lift (Lift m) l) => EffectLift m l

-- | Lifts a monadic value into an effect.
lift :: EffectLift m l => m a -> Effect l a
lift = send . Lift

-- | Lifts a monadic value into an effect.
liftEffect :: EffectLift m l => m (Effect l a) -> Effect l a
liftEffect = sendEffect . Lift

-- | Converts a computation containing only monadic
-- effects into a monadic computation.
runLift :: Monad m => Effect (Lift m ':+ 'Nil) a -> m a
runLift = runEffect . eliminate
    (return . return)
    (\(Lift m) k -> return $ m >>= runEffect . k)
