-- |This implementation of the effect monad uses `Free` over a -- `Union` of functors and then applies `Codensity` over it -- for asymptotic improvements of ill-associated binds. module Control.Effects.Eff ( Eff , Handler , Comp (Value, Comp) , Res , effect , runPure , runPureRes , handle , continue , finish , inj , Member , Typeable ) where import Control.Applicative import Control.Monad.Codensity import Control.Monad.Free import Data.Union import Data.Typeable -- |Result structure of the program is directly `Free` over `Union` -- indexed by the list of effect functors. type Res r = Free (Union r) newtype Eff r a = Eff { runEff :: Codensity (Res r) a } deriving (Functor, Applicative, Monad) -- |Comp represents a computation. It is either a pure value or a computation -- that needs further evaluation and effect handling. data Comp e r a b = Value a | Comp (e (Res r b)) -- |Handler is a function that takes a result or an effect and a continuation -- |and handles it. -- -- `e` is the effect functor you are handling -- -- `r` represents the type of the type list of the remaining effects. -- Usually you want to be polymorphic in this. -- -- `a` is the result type of the program you will handle -- -- `b` is the result of handled computation. type Handler e r a b = Comp e r a b -> Res r b -- | `effect` is meant to be used as a helper function for defining new effects. -- See predefined effects for examples. Good way to use it is to pass in a lambda -- expression with explicit `k` for continuation. You will need to manually `inj` -- into the `Union` because of some GHC limitations. effect :: (forall b . (a -> Res r b) -> Union r (Res r b)) -> Eff r a effect e = Eff $ Codensity $ \k -> Free $ e k -- |A program without effects is guaranteed to be pure so you -- can safely convert it into a value. runPure :: Eff '[] a -> a runPure = runPureRes . finish -- |Like `runPure` but for program results. You only need this for implementing -- some handlers. runPureRes :: Res '[] a -> a runPureRes (Pure a) = a -- |Finish a program and convert it into a result structure. finish :: Eff r a -> Res r a finish = lowerCodensity . runEff -- |Convert a result back into a program in order to compose it. -- This function might not be needed and might introduce some -- performance issues (it is used in `handle`) but we didn't find -- a way to drop it. continue :: Res r a -> Eff r a continue r = Eff $ Codensity (r >>=) handleRes :: (Functor e, Typeable e) => Handler e r a b -> Res (e ': r) a -> Res r b handleRes h (Pure a) = h $ Value a handleRes h (Free u) = case decomp u of Left u -> h $ Comp $ fmap (handleRes h) u Right u -> Free $ fmap (handleRes h) u -- |Use a `Handler` on an `Eff` program to stripe away the first layer of effects. -- There are some issues if you are using a handler that is somewhat polymorphic in `e` -- As the compiler cannot figure out which effect are you handling. Currently the best -- solution seems to be to manually specify type of the handler such that it is monomorphic -- in `e`. Sorry. handle :: (Functor e, Typeable e) => Handler e r a b -> Eff (e ': r) a -> Eff r b handle h c = continue $ handleRes h $ finish c