{-# OPTIONS_HADDOCK show-extensions #-}

{-# LANGUAGE GADTs #-} 
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RankNTypes #-}

{-|
Module      : Control.THEff
Description : Main module of TH Eff package. 
Copyright   : (c) Kolodezny Diver, 2015
License     : GPL-3
Maintainer  : kolodeznydiver@gmail.com
Stability   : experimental
Portability : Portable
-}

module Control.THEff (
                      -- * Overview 
-- |
--   This package implements effects, as alternative to monad
--   transformers. Actually, the effects themselves are created without 
--   the use of TH, but the binding of nested effects described by 
--   mkEff splice. For example.
--   
-- > {-# LANGUAGE KindSignatures #-}
-- > {-# LANGUAGE FlexibleInstances #-}
-- > {-# LANGUAGE MultiParamTypeClasses #-}
-- > {-# LANGUAGE TemplateHaskell #-}
-- > {-# LANGUAGE ScopedTypeVariables #-} 
-- > 
-- > import Control.THEff
-- > import Control.THEff.Reader
-- > import Control.THEff.State
-- >
-- > mkEff "MyReader"    ''Reader    ''Int       ''Lift
-- > mkEff "SomeState"   ''State     ''Bool      ''MyReader
-- > mkEff "OtherRdr"    ''Reader    ''Float     ''SomeState
-- > 
-- > main:: IO ()
-- > main = do
-- >     r <- runMyReader  100 
-- >        $ runSomeState False
-- >        $ runOtherRdr  pi  $ do
-- >             i :: Int   <- ask                    -- MyReader 
-- >             f :: Float <- ask                    -- OtherRdr
-- >             b <- get                             -- SomeState
-- >             put $ not b                          -- SomeState 
-- >             lift $ putStrLn "print from effect!" -- Lift  
-- >             return $ show $ fromIntegral i * f 
-- >     print r
--  
--  For more information about extensible effects , see the original paper at
--  <http://okmij.org/ftp/Haskell/extensible/exteff.pdf>.
--  But, this package is significantly different from the original.
--  It uses a chains of ordinary GADTs created by TH. 
--  No Typeable, unsafe... , ExistentialQuantification ...
--  
--  Note. Further, wherever referred to  __/runEEEE/__ is meant `mkEff' generated function, e.g.
--  runMyReader, runSomeState, runOtherRdr .
-- 
--  See more in samples/*.hs

                      -- * Base THEff support
                      mkEff
                    , Eff(..)
                    , EffClass(..)
                      -- * No monadic start effect 
                    , NoEff(..)
                    , effNoEff
                    , runNoEff
                      -- * Monadic start effect
                    , Lift'(..)
                    , EffClassM(..)
                    , lift
                    , Lift(..)
                    , runLift
                     )  where

import Control.THEff.TH.Internal(mkEff)

-- | The Monad of effects
newtype Eff w a = Eff {runEff :: (a -> w) -> w}

instance Functor (Eff w) where
      fmap f (Eff g) = Eff $ \k -> g (k . f)

instance Applicative (Eff w) where
    pure = return
    Eff f <*> Eff g = Eff $ \k -> f (\v -> g (k . v))
      
instance Monad (Eff w) where
    return x = Eff $ \k -> k x
    m >>= f =  Eff $ \k -> runEff m (\v -> runEff (f v) k)

-- | Helper class to transfer the action effects by chain. 
--   Instances of this class are created in @mkEff@.
class EffClass w v e where
    effAction:: ((r -> e) -> w v e) -> Eff e r 
    effAction f = Eff $ \k -> toEff $ f k
    toEff:: w v e -> e

-- | The first effect in a chain of effects not use monads.
--   The chain of effects should start or that type, or @Lift@ (See below.)
newtype NoEff (m:: * -> *) a = NoEff { unNoEff :: a}

-- | This function is used in the 'mkEff' generated runEEEE... functions. 
-- @effNoEff _ = error "THEff: Attempting to call the effect NoEff that does not have any actions!"@
effNoEff :: a -> b
effNoEff _ = error "THEff: Attempting to call the effect NoEff that does not have any actions!"

-- | This function is used in the 'mkEff' generated runEEEE... functions. 
--   Do not use it alone.
runNoEff :: Eff (NoEff m a) a -> a
runNoEff m = unNoEff $ runEff m NoEff

-- | Helper data type for transfer the monadic action effects by chain. 
data Lift' m v = forall a. Lift' (m a) (a -> v)

-- | Helper class to transfer the monadic action effects by chain. 
--   Instances of this class are created in @mkEff@.
class EffClassM m e where
    effLift:: Lift' m r -> Eff e r
    effLift (Lift' m g) = Eff $ \k -> toEffM $ Lift' m (k . g)
    -- | 
    toEffM:: Lift' m e -> e

-- | Lift a Monad to an Effect.
lift:: (Monad m, EffClassM m e) => m a -> Eff e a
lift m = effLift $ Lift' m id

-- | The first effect in a chain of monadic effects.
--   The chain of effects should start or that type, or @NoEff@.
data Lift m a = Lift_ (Lift' m (Lift m a))
              | LiftResult a

instance EffClassM m (Lift m a) where
    toEffM = Lift_

-- | This function is used in the @mkEff@ generated runEEEE... functions. 
--   Do not use it alone.
runLift :: Monad m => Eff (Lift m a) a -> m a
runLift e = loop $ runEff e LiftResult
    where
        loop (Lift_ (Lift' m g)) = m >>= loop . g 
        loop (LiftResult r) = return r