{-|
Module      : Control.Monad.EitherCont
Description : The 'EitherCont' type and API
Copyright   : (c) Eitan Chatav, 2015
License     : PublicDomain
Maintainer  : eitan.chatav@gmail.com
Stability   : experimental

The 'EitherCont' type and API provide an idiomatic way to handle errors in
continuation passing style.
-}

module Control.Monad.EitherCont
    ( EitherCont
    , EitherContT()
    , eitherCont
    , runEitherCont
    , liftEither
    , fmapL
    , bimapEC
    , throwEC
    , apL
    , catchEC
    , liftL
    , flipEC
    , mapEitherCont
    , withEitherContL
    , withEitherContR
    , callCCL
    ) where

import Control.Monad.Trans.EitherCont
import Data.Functor.Identity

-- |'EitherCont' 'a' 'l' 'r' is a CPS computation that produces an intermediate
-- result of type 'a' within a CPS computation which produces either a success
-- of type 'r' or failure of type 'l'.
type EitherCont a l r = EitherContT a l Identity r

-- |Construct a continuation-passing computation from a function.
eitherCont :: ((l -> a) -> (r -> a) -> a) -> EitherCont a l r
eitherCont ec = EitherContT $ \kl kr -> Identity $
    ec (runIdentity . kl) (runIdentity . kr)

-- |The result of running a CPS computation with given failure and success
-- continuations.
--
-- prop> runEitherCont . eitherCont = id
-- prop> eitherCont . runEitherCont = id
runEitherCont :: EitherCont a l r -> (l -> a) -> (r -> a) -> a
runEitherCont ec kl kr = runIdentity $
    runEitherContT ec (Identity . kl) (Identity . kr)

-- |'liftEither' embeds 'Either' in 'EitherCont' 'a'.
liftEither :: Either l r -> EitherCont a l r
liftEither e = eitherCont $ \kl kr -> either kl kr e

-- |Apply a function to transform the result of a continuation-passing
-- computation.
mapEitherCont :: (a -> a) -> EitherCont a l r -> EitherCont a l r
mapEitherCont = mapEitherContT . fmap

-- |Apply a function to transform the success continuation passed to a
-- continuation-passing computation.
withEitherContR :: ((r' -> a) -> r -> a)
                -> EitherCont a l r
                -> EitherCont a l r'
withEitherContR f = withEitherContTR ((Identity .) . f . (runIdentity .))

-- |Apply a function to transform the failure continuation passed to an
-- continuation-passing computation.
withEitherContL :: ((l' -> a) -> l -> a)
                -> EitherCont a l r
                -> EitherCont a l' r
withEitherContL f = withEitherContTL ((Identity .) . f . (runIdentity .))