module FP.Prelude.Effects where

import FP.Prelude.Core
import FP.Prelude.Morphism
import FP.Prelude.Lens

-- 4: sums
infixr 4 <>       -- (<⧺>) ∷ (MonadMonoid m) ⇒ m a → m a → m a
infixr 4 <>       -- (<⊔>) ∷ (MonadJoin m) ⇒ m a → m a → m a

-- 5: products
infixr 5 <>       -- (<⊓>) ∷ (MonadMeet m) ⇒ m a → m a → m a

-- # List of effects
--
-- - Failure
-- - Error
-- - Reader
-- - Writer
-- - State
-- - Continuation
-- - OpaqueContinuation

class MonadMonoid (m    ) where {mnull  m a;(<>)  m a  m a  m a}
class MonadBot (m    ) where mbot  m a
class MonadJoin (m    ) where (<>)  m a  m a  m a
class MonadTop (m    ) where mtop  m a
class MonadMeet (m    ) where (<>)  m a  m a  m a
class (MonadBot m,MonadJoin m)  MonadJoinLattice m

mnullMaybe  (Monad m,MonadMonoid m)  Maybe a  m a
mnullMaybe = elimMaybe mnull return

mbotMaybe  (Monad m,MonadBot m)  Maybe a  m a
mbotMaybe = elimMaybe mbot return

mconcat  (MonadMonoid m,ToFold (m a) t)  t  m a
mconcat = foldr (<>) mnull

mlist  (Monad m,MonadMonoid m,ToFold a t)  t  m a
mlist = foldr ((<>)  return) mnull

msum  (MonadJoinLattice m,ToFold (m a) t)  t  m a
msum = iter (<>) mbot

mset  (Monad m,MonadJoinLattice m,ToFold a t)  t  m a
mset = iter ((<>)  return) mbot


type MonadRWS r o s m = (MonadReader r m,MonadWriter o m,MonadState s m)

-- # Failure

newtype FailureT m a = FailureT {runFailureT  m (Maybe a)}
class MonadFailure (m    ) where 
  failureE  FailureT m  m
  failureI  m  FailureT m

-- Effectors

effMaybe  (MonadFailure m)  m (Maybe a)  m a 
effMaybe = failureE  FailureT

abort  (Monad m,MonadFailure m)  m a 
abort = effMaybe $ return Nothing

abortMaybe  (Monad m,MonadFailure m)  Maybe a  m a
abortMaybe = elimMaybe abort return

-- Observers

obsMaybe  (MonadFailure m)  m a  m (Maybe a) 
obsMaybe = runFailureT  failureI

(<|>)  (Monad m,MonadFailure m)  m a  m a  m a
aM1 <|> aM2 = do
  aM'  obsMaybe aM1
  case aM' of
    Nothing  aM2
    Just a  return a

-- Combos

tries  (Monad m,MonadFailure m)  [m a]  m a
tries = foldr (<|>) abort

-- # Error

newtype ErrorT e m a = ErrorT {runErrorT  m (e  a)}
class MonadError e (m    ) | m  e where 
  errorE  ErrorT e m  m
  errorI  m  ErrorT e m

mapError  (Functor m)  (e1  e2)  ErrorT e1 m a  ErrorT e2 m a
mapError f = ErrorT  mapLeft f ^ runErrorT

-- Effectors

effError  (MonadError e m)  m (e  a)  m a  
effError = errorE  ErrorT

throw  (Monad m,MonadError e m)  e  m a            
throw e = effError $ return $ Left e

throwSum  (Monad m,MonadError e m)  e  a  m a      
throwSum = elimSum throw return

throwMaybe  (Monad m,MonadError e m)  e  Maybe a  m a 
throwMaybe e = elimMaybe (throw e) return

-- Observers

obsError  (MonadError e m)  m a  m (e  a)  
obsError = runErrorT  errorI

catch  (Monad m,MonadError e m)  m a  (e  m a)  m a
catch aM h = do
  aeM  runErrorT $ errorI aM
  case aeM of
    Left e  h e
    Right a  return a

-- # Reader

newtype ReaderT r m a = ReaderT {runReaderT  r  m a}
class MonadReader r (m    ) | m  r where 
  readerE  ReaderT r m  m
  readerI  m  ReaderT r m

runReaderTWith  r  ReaderT r m a  m a
runReaderTWith = flip runReaderT

-- Effectors

effReader  (MonadReader r m)  (r  m a)  m a 
effReader = readerE  ReaderT

ask  (Monad m,MonadReader r m)  m r 
ask = effReader return

askL  (Monad m,MonadReader r m)  Lens r a  m a 
askL l = access l ^$ ask

-- Observers

obsReader  (MonadReader r m)  m a  (r  m a) 
obsReader = runReaderT  readerI

-- Combos

local  (MonadReader r m)  (r  r)  m a  m a 
local f aM = effReader $ obsReader aM  f

localOn  (MonadReader r m)  m a  (r  r)  m a
localOn = flip local

-- # Writer

newtype WriterT o m a = WriterT {runWriterT  m (a,o)}
class MonadWriter o m | m  o where 
  writerE  WriterT o m  m
  writerI  m  WriterT o m

execWriterT  (Functor m)  WriterT o m a  m o
execWriterT = snd ^ runWriterT

mapOutput  (Functor m)  (o₁  o₂)  WriterT o₁ m a  WriterT o₂ m a
mapOutput f = WriterT  mapSnd f ^ runWriterT

-- Effectors

effWriter  (MonadWriter o m)  m (a,o)  m a 
effWriter = writerE  WriterT

tell  (Monad m,MonadWriter o m)  o  m () 
tell = effWriter  return  ((),)

-- Observers

obsWriter  (MonadWriter o m)  m a  m (a,o) 
obsWriter = runWriterT  writerI

hijack  (MonadWriter o m)  m a  m (a,o) 
hijack = obsWriter

-- # State

newtype StateT s m a = StateT {runStateT  s  m (a,s)}
class MonadState s m | m  s where 
  stateE  StateT s m  m 
  stateI  m  StateT s m

runStateTWith  s  StateT s m a  m (a,s)
runStateTWith = flip runStateT

evalStateTWith  (Functor m)  s  StateT s m a  m a
evalStateTWith s = map fst  runStateTWith s

execStateTWith  (Functor m)  s  StateT s m a  m s
execStateTWith s = map snd  runStateTWith s

stateLens  (Functor m)  Lens s₁ s₂  StateT s₂ m  StateT s₁ m
stateLens l aM = StateT $ \ s₁ 
  let s₂ = access l s₁
      ff (a,s₂') = (a,update l s₂' s₁)
  in ff ^$ runStateT aM s₂

-- Effectors

effState  (MonadState s m)  (s  m (a,s))  m a 
effState = stateE  StateT

get  (Monad m,MonadState s m)  m s 
get = stateE $ StateT $ \ s  return (s,s)

getL  (Monad m,MonadState s m)  Lens s a  m a 
getL l = map (access l) get

put  (Monad m,MonadState s m)  s  m () 
put s = stateE $ StateT $ \ _  return ((),s)

putL  (Monad m,MonadState s m)  Lens s a  a  m () 
putL = modify  update

modifyM  (Monad m,MonadState s m)  (s  m s)  m () 
modifyM f = stateE $ StateT $ \ s  return () <×> f s

modifyLM  (Monad m,MonadState s m)  Lens s a  (a  m a)  m () 
modifyLM  = modifyM  alterM

modify  (Monad m,MonadState s m)  (s  s)  m () 
modify = modifyM  kreturn

modifyL  (Monad m,MonadState s m)  Lens s a  (a  a)  m () 
modifyL = modify  alter

getAndPut  (Monad m,MonadState s m)  s  m s
getAndPut s = do
  s'  get
  put s
  return s'

getAndPutL  (Monad m,MonadState s m)  Lens s a  a  m a
getAndPutL 𝓁 x = do
  x'  getL 𝓁
  putL 𝓁 x
  return x'

next  (Monad m,MonadState s m,Peano s)  m s
next = do
  i  get
  put $ suc i
  return i

nextL  (Monad m,MonadState s m,Peano a)  Lens s a  m a
nextL l = do
  i  getL l
  putL l $ suc i
  return i

bump  (Monad m,MonadState s m,Peano s)  m ()
bump = modify suc

bumpL  (Monad m,MonadState s m,Peano a)  Lens s a  m ()
bumpL l = modifyL l suc

-- Observers

obsState  (MonadState s m)  m a  (s  m (a,s)) 
obsState = runStateT  stateI

localize  (Monad m,MonadState s m)  m a  m (a,s)
localize aM = obsState aM *$ get

-- # Nondeterminism

newtype NondetAppendT m a = NondetAppendT { runNondetAppendT  m [a] }
class MonadNondetAppend m where
  nondetAppendE  NondetAppendT m  m
  nondetAppendI  m  NondetAppendT m

newtype NondetJoinT m a = NondetJoinT { runNondetJoinT  m (𝒫ᵇ a) }
class MonadNondetJoin m where
  nondetJoinE  NondetJoinT m  m
  nondetJoinI  m  NondetJoinT m

-- # Flow

newtype FlowAppendT s m a = FlowAppendT { runFlowAppendT  s  m (a  s) }
class MonadFlowAppend s m | m  s where
  flowAppendE  FlowAppendT s m  m
  flowAppendI  m  FlowAppendT s m

newtype FlowJoinT s m a = FlowJoinT { runFlowJoinT  s  m (a  s) }
class MonadFlowJoin s m | m  s where
  flowJoinE  FlowJoinT s m  m
  flowJoinI  m  FlowJoinT s m

-- # Cont

newtype ContT r m a = ContT {runContT  (a  m r)  m r}
class MonadCont r m | m  r where 
  contE  ContT r m  m
  contI  m  ContT r m

evalContT  (Monad m)  ContT r m r  m r
evalContT aM = runContT aM return

-- Effectors

effCont  (MonadCont r m)  ((a  m r)  m r)  m a 
effCont = contE  ContT

callCC  (MonadCont r m)  ((a  m r)  m r)  m a 
callCC = effCont

-- Observers

obsCont  (MonadCont r m)  m a  ((a  m r)  m r) 
obsCont = runContT  contI

withC  (MonadCont r m)  (a  m r)  m a  m r 
withC = flip obsCont

-- Combos

reset  (Monad m,MonadCont r m)  m r  m r 
reset aM = callCC $ \ k  k *$ withC return aM

modifyC  (Monad m,MonadCont r m)  (r  m r)  m a  m a 
modifyC f aM = callCC $ \ k  withC (f * k) aM

-- # OpaqueCont

newtype OpaqueContT k r m a = OpaqueContT {runOpaqueContT  k r m a  m r}
class MonadOpaqueCont k r m | m  k,m  r where 
  opaqueContE  OpaqueContT k r m  m
  opaqueContI  m  OpaqueContT k r m

newtype ContFun r m a = ContFun {runContFun  a  m r}

runOpaqueContTWith  k r m a  OpaqueContT k r m a  m r
runOpaqueContTWith = flip runOpaqueContT

evalOpaqueContT  (Monad m,Isomorphic3 (ContFun r) (k r))  OpaqueContT k r m r  m r
evalOpaqueContT aM = runMetaContT aM return

runMetaContT  (Isomorphic3 (ContFun r) (k r))  OpaqueContT k r m a  (a  m r)  m r
runMetaContT aM k = runOpaqueContT aM $ isoTo3 isomorphic3 $ ContFun k

runMetaContTWith  (Isomorphic3 (ContFun r) (k r))  (a  m r)  OpaqueContT k r m a  m r
runMetaContTWith = flip runMetaContT

metaContT  (Isomorphic3 (ContFun r) (k r))  ((a  m r)  m r)  OpaqueContT k r m a
metaContT nk = OpaqueContT $ \ (k  k r m a)  nk $ runContFun $ isoFrom3 isomorphic3 k

meta  (Isomorphic3 (ContFun r) (k r))  OpaqueContT k r m  ContT r m
meta aM = ContT $ \ (k  a  m r)  runMetaContT aM k

opaque  (Isomorphic3 (ContFun r) (k r))  ContT r m  OpaqueContT k r m
opaque aM = metaContT $ \ (k  a  m r)  runContT aM k

-- Effectors

effOpaqueCont  (MonadOpaqueCont k r m)  (k r m a  m r)  m a 
effOpaqueCont = opaqueContE  OpaqueContT

callCCOpaque  (MonadOpaqueCont k r m)  (k r m a  m r)  m a 
callCCOpaque = effOpaqueCont

-- Observers

obsOpaqueCont  (MonadOpaqueCont k r m)  m a  (k r m a  m r) 
obsOpaqueCont = runOpaqueContT  opaqueContI

withCOpaque  (MonadOpaqueCont k r m)  k r m a  m a  m r 
withCOpaque  = flip obsOpaqueCont