{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
module Data.Extensible.Effect (
Instruction(..)
, Eff
, liftEff
, liftsEff
, hoistEff
, castEff
, Interpreter(..)
, handleEff
, peelEff
, Rebinder
, rebindEff0
, peelEff0
, rebindEff1
, peelEff1
, rebindEff2
, leaveEff
, retractEff
, Action(..)
, Function
, runAction
, (@!?)
, peelAction
, peelAction0
, ReaderEff
, askEff
, asksEff
, localEff
, runReaderEff
, State
, getEff
, getsEff
, putEff
, modifyEff
, stateEff
, runStateEff
, execStateEff
, evalStateEff
, WriterEff
, writerEff
, tellEff
, listenEff
, passEff
, runWriterEff
, execWriterEff
, MaybeEff
, nothingEff
, runMaybeEff
, EitherEff
, throwEff
, catchEff
, runEitherEff
, mapLeftEff
, Identity
, tickEff
, runIterEff
, ContT
, contEff
, runContEff
, callCCEff
) where
import Control.Applicative
import Data.Bifunctor (first)
import Control.Monad.Skeleton
import Control.Monad.Trans.State.Strict
import Control.Monad.Trans.Cont (ContT(..))
import Data.Extensible.Field
import Data.Extensible.Inclusion
import Data.Extensible.Internal.Rig
import Data.Extensible.Product
import Data.Extensible.Class
import Data.Functor.Identity
import Data.Profunctor.Unsafe
import Data.Type.Equality
import Type.Membership
data Instruction (xs :: [Assoc k (* -> *)]) a where
Instruction :: !(Membership xs kv) -> TargetOf kv a -> Instruction xs a
type Eff xs = Skeleton (Instruction xs)
liftEff :: forall s t xs a. Lookup xs s t => Proxy s -> t a -> Eff xs a
liftEff p x = liftsEff p x id
{-# INLINE liftEff #-}
liftsEff :: forall s t xs a r. Lookup xs s t
=> Proxy s -> t a -> (a -> r) -> Eff xs r
liftsEff _ x k = boned
$ Instruction (association :: Membership xs (s ':> t)) x :>>= return . k
{-# INLINE liftsEff #-}
hoistEff :: forall s t xs a. Lookup xs s t => Proxy s -> (forall x. t x -> t x) -> Eff xs a -> Eff xs a
hoistEff _ f = hoistSkeleton $ \(Instruction i t) -> case compareMembership (association :: Membership xs (s ':> t)) i of
Right Refl -> Instruction i (f t)
_ -> Instruction i t
{-# INLINABLE hoistEff #-}
castEff :: IncludeAssoc ys xs => Eff xs a -> Eff ys a
castEff = hoistSkeleton
$ \(Instruction i t) -> Instruction (hlookup i inclusionAssoc) t
peelEff :: forall k t xs a r
. Rebinder xs r
-> (a -> r)
-> (forall x. t x -> (x -> r) -> r)
-> Eff (k >: t ': xs) a -> r
peelEff pass ret wrap = go where
go m = case debone m of
Return a -> ret a
Instruction i t :>>= k -> testMembership i
(\Refl -> wrap t (go . k))
(\j -> pass (Instruction j t) (go . k))
{-# INLINE peelEff #-}
peelEff0 :: forall k t xs a r. (a -> Eff xs r)
-> (forall x. t x -> (x -> Eff xs r) -> Eff xs r)
-> Eff (k >: t ': xs) a -> Eff xs r
peelEff0 = peelEff rebindEff0
{-# INLINE peelEff0 #-}
peelEff1 :: forall k t xs a b r. (a -> b -> Eff xs r)
-> (forall x. t x -> (x -> b -> Eff xs r) -> b -> Eff xs r)
-> Eff (k >: t ': xs) a -> b -> Eff xs r
peelEff1 = peelEff rebindEff1
{-# INLINE peelEff1 #-}
type Rebinder xs r = forall x. Instruction xs x -> (x -> r) -> r
rebindEff0 :: Rebinder xs (Eff xs r)
rebindEff0 i k = boned $ i :>>= k
rebindEff1 :: Rebinder xs (a -> Eff xs r)
rebindEff1 i k a = boned $ i :>>= flip k a
rebindEff2 :: Rebinder xs (a -> b -> Eff xs r)
rebindEff2 i k a b = boned $ i :>>= \x -> k x a b
leaveEff :: Eff '[] a -> a
leaveEff m = case debone m of
Return a -> a
_ -> error "Impossible"
retractEff :: forall k m a. Monad m => Eff '[k >: m] a -> m a
retractEff m = case debone m of
Return a -> return a
Instruction i t :>>= k -> testMembership i
(\Refl -> t >>= retractEff . k)
$ error "Impossible"
newtype Interpreter f g = Interpreter { runInterpreter :: forall a. g a -> f a }
handleEff :: RecordOf (Interpreter m) xs -> Eff xs a -> MonadView m (Eff xs) a
handleEff hs m = case debone m of
Instruction i t :>>= k -> views (pieceAt i) (runInterpreter .# getField) hs t :>>= k
Return a -> Return a
data Action (args :: [*]) a r where
AResult :: Action '[] a a
AArgument :: x -> Action xs a r -> Action (x ': xs) a r
type family Function args r :: * where
Function '[] r = r
Function (x ': xs) r = x -> Function xs r
runAction :: Function xs (f a) -> Action xs a r -> f r
runAction r AResult = r
runAction f (AArgument x a) = runAction (f x) a
(@!?) :: FieldName k -> Function xs (f a) -> Field (Interpreter f) (k ':> Action xs a)
_ @!? f = Field $ Interpreter $ runAction f
infix 1 @!?
peelAction :: forall k ps q xs a r
. (forall x. Instruction xs x -> (x -> r) -> r)
-> (a -> r)
-> Function ps ((q -> r) -> r)
-> Eff (k >: Action ps q ': xs) a -> r
peelAction pass ret wrap = go where
go m = case debone m of
Return a -> ret a
Instruction i t :>>= k -> testMembership i
(\Refl -> case t of
(_ :: Action ps q x) ->
let run :: forall t. Function t ((q -> r) -> r) -> Action t q x -> r
run f AResult = f (go . k)
run f (AArgument x a) = run (f x) a
in run wrap t)
$ \j -> pass (Instruction j t) (go . k)
{-# INLINE peelAction #-}
peelAction0 :: forall k ps q xs a. Function ps (Eff xs q)
-> Eff (k >: Action ps q ': xs) a -> Eff xs a
peelAction0 wrap = go where
go m = case debone m of
Return a -> return a
Instruction i t :>>= k -> testMembership i
(\Refl -> case t of
(_ :: Action ps q x) ->
let run :: forall t. Function t (Eff xs q) -> Action t q x -> Eff xs a
run f AResult = f >>= go . k
run f (AArgument x a) = run (f x) a
in run wrap t)
$ \j -> rebindEff0 (Instruction j t) (go . k)
{-# INLINE peelAction0 #-}
type ReaderEff = (:~:)
askEff :: forall k r xs. Lookup xs k (ReaderEff r)
=> Proxy k -> Eff xs r
askEff p = liftEff p Refl
{-# INLINE askEff #-}
asksEff :: forall k r xs a. Lookup xs k (ReaderEff r)
=> Proxy k -> (r -> a) -> Eff xs a
asksEff p = liftsEff p Refl
{-# INLINE asksEff #-}
localEff :: forall k r xs a. Lookup xs k (ReaderEff r)
=> Proxy k -> (r -> r) -> Eff xs a -> Eff xs a
localEff _ f = go where
go m = case debone m of
Return a -> return a
Instruction i t :>>= k -> case compareMembership
(association :: Membership xs (k >: ReaderEff r)) i of
Left _ -> boned $ Instruction i t :>>= go . k
Right Refl -> case t of
Refl -> boned $ Instruction i t :>>= go . k . f
{-# INLINE localEff #-}
runReaderEff :: forall k r xs a. Eff (k >: ReaderEff r ': xs) a -> r -> Eff xs a
runReaderEff m r = peelEff0 return (\Refl k -> k r) m
{-# INLINE runReaderEff #-}
getEff :: forall k s xs. Lookup xs k (State s)
=> Proxy k -> Eff xs s
getEff k = liftEff k get
{-# INLINE getEff #-}
getsEff :: forall k s a xs. Lookup xs k (State s)
=> Proxy k -> (s -> a) -> Eff xs a
getsEff k = liftsEff k get
{-# INLINE getsEff #-}
putEff :: forall k s xs. Lookup xs k (State s)
=> Proxy k -> s -> Eff xs ()
putEff k = liftEff k . put
{-# INLINE putEff #-}
modifyEff :: forall k s xs. Lookup xs k (State s)
=> Proxy k -> (s -> s) -> Eff xs ()
modifyEff k f = liftEff k $ state $ \s -> ((), f s)
{-# INLINE modifyEff #-}
stateEff :: forall k s xs a. Lookup xs k (State s)
=> Proxy k -> (s -> (a, s)) -> Eff xs a
stateEff k = liftEff k . state
{-# INLINE stateEff #-}
contState :: State s a -> (a -> s -> r) -> s -> r
contState m k s = let (a, s') = runState m s in k a $! s'
runStateEff :: forall k s xs a. Eff (k >: State s ': xs) a -> s -> Eff xs (a, s)
runStateEff = peelEff1 (\a s -> return (a, s)) contState
{-# INLINE runStateEff #-}
execStateEff :: forall k s xs a. Eff (k >: State s ': xs) a -> s -> Eff xs s
execStateEff = peelEff1 (const return) contState
{-# INLINE execStateEff #-}
evalStateEff :: forall k s xs a. Eff (k >: State s ': xs) a -> s -> Eff xs a
evalStateEff = peelEff1 (const . return) contState
{-# INLINE evalStateEff #-}
type WriterEff w = (,) w
writerEff :: forall k w xs a. (Lookup xs k (WriterEff w))
=> Proxy k -> (a, w) -> Eff xs a
writerEff k (a, w) = liftEff k (w, a)
{-# INLINE writerEff #-}
tellEff :: forall k w xs. (Lookup xs k (WriterEff w))
=> Proxy k -> w -> Eff xs ()
tellEff k w = liftEff k (w, ())
{-# INLINE tellEff #-}
listenEff :: forall k w xs a. (Lookup xs k (WriterEff w), Monoid w)
=> Proxy k -> Eff xs a -> Eff xs (a, w)
listenEff p = go mempty where
go w m = case debone m of
Return a -> writerEff p ((a, w), w)
Instruction i t :>>= k -> case compareMembership (association :: Membership xs (k ':> (,) w)) i of
Left _ -> boned $ Instruction i t :>>= go w . k
Right Refl -> let (w', a) = t
!w'' = mappend w w' in go w'' (k a)
{-# INLINE listenEff #-}
passEff :: forall k w xs a. (Lookup xs k (WriterEff w), Monoid w)
=> Proxy k -> Eff xs (a, w -> w) -> Eff xs a
passEff p = go mempty where
go w m = case debone m of
Return (a, f) -> writerEff p (a, f w)
Instruction i t :>>= k -> case compareMembership (association :: Membership xs (k ':> (,) w)) i of
Left _ -> boned $ Instruction i t :>>= go w . k
Right Refl -> let (w', a) = t
!w'' = mappend w w' in go w'' (k a)
{-# INLINE passEff #-}
contWriter :: Monoid w => (w, a) -> (a -> w -> r) -> w -> r
contWriter (w', a) k w = k a $! mappend w w'
runWriterEff :: forall k w xs a. Monoid w => Eff (k >: WriterEff w ': xs) a -> Eff xs (a, w)
runWriterEff = peelEff1 (\a w -> return (a, w)) contWriter `flip` mempty
{-# INLINE runWriterEff #-}
execWriterEff :: forall k w xs a. Monoid w => Eff (k >: WriterEff w ': xs) a -> Eff xs w
execWriterEff = peelEff1 (const return) contWriter `flip` mempty
{-# INLINE execWriterEff #-}
type MaybeEff = Const ()
nothingEff :: Lookup xs k MaybeEff => Proxy k -> Eff xs a
nothingEff = flip throwEff ()
runMaybeEff :: forall k xs a. Eff (k >: MaybeEff ': xs) a -> Eff xs (Maybe a)
runMaybeEff = peelEff0 (return . Just) $ \_ _ -> return Nothing
{-# INLINE runMaybeEff #-}
type EitherEff = Const
throwEff :: Lookup xs k (EitherEff e) => Proxy k -> e -> Eff xs a
throwEff k = liftEff k . Const
{-# INLINE throwEff #-}
catchEff :: forall k e xs a. (Lookup xs k (EitherEff e))
=> Proxy k -> Eff xs a -> (e -> Eff xs a) -> Eff xs a
catchEff _ m0 handler = go m0 where
go m = case debone m of
Return a -> return a
Instruction i t :>>= k -> case compareMembership (association :: Membership xs (k ':> Const e)) i of
Left _ -> boned $ Instruction i t :>>= go . k
Right Refl -> handler (getConst t)
{-# INLINE catchEff #-}
runEitherEff :: forall k e xs a. Eff (k >: EitherEff e ': xs) a -> Eff xs (Either e a)
runEitherEff = peelEff0 (return . Right) $ \(Const e) _ -> return $ Left e
{-# INLINE runEitherEff #-}
tickEff :: Lookup xs k Identity => Proxy k -> Eff xs ()
tickEff k = liftEff k $ Identity ()
{-# INLINE tickEff #-}
mapHeadEff :: (forall x. s x -> t x) -> Eff ((k >: s) ': xs) a -> Eff ((k' >: t) ': xs) a
mapHeadEff f = hoistSkeleton $ \(Instruction i t) -> testMembership i
(\Refl -> Instruction leadership $ f t)
(\j -> Instruction (nextMembership j) t)
mapLeftEff :: (e -> e') -> Eff ((k >: EitherEff e) ': xs) a -> Eff ((k >: EitherEff e') ': xs) a
mapLeftEff f = mapHeadEff (first f)
runIterEff :: Eff (k >: Identity ': xs) a
-> Eff xs (Either a (Eff (k >: Identity ': xs) a))
runIterEff m = case debone m of
Return a -> return (Left a)
Instruction i t :>>= k -> testMembership i
(\Refl -> return $ Right $ k $ runIdentity t)
$ \j -> boned $ Instruction j t :>>= runIterEff . k
contEff :: Lookup xs k (ContT r m) => Proxy k
-> ((a -> m r) -> m r) -> Eff xs a
contEff k = liftEff k . ContT
runContEff :: forall k r xs a. Eff (k >: ContT r (Eff xs) ': xs) a
-> (a -> Eff xs r)
-> Eff xs r
runContEff m cont = case debone m of
Return a -> cont a
Instruction i t :>>= k -> testMembership i
(\Refl -> runContT t (flip runContEff cont . k))
$ \j -> boned $ Instruction j t :>>= flip runContEff cont . k
callCCEff :: Proxy k -> ((a -> Eff ((k >: ContT r (Eff xs)) : xs) b) -> Eff ((k >: ContT r (Eff xs)) : xs) a) -> Eff ((k >: ContT r (Eff xs)) : xs) a
callCCEff k f = contHead k . ContT $ \c -> runContEff (f (\x -> contHead k . ContT $ \_ -> c x)) c
where
contHead :: Proxy k -> ContT r (Eff xs) a -> Eff ((k >: ContT r (Eff xs)) ': xs) a
contHead _ c = boned $ Instruction leadership c :>>= return