simple-effects 0.9.0.1 → 0.10.0.0
raw patch · 16 files changed
+1675/−277 lines, 16 filesdep +asyncdep +bytestringdep ~MonadRandomdep ~arraydep ~basePVP ok
version bump matches the API change (PVP)
Dependencies added: async, bytestring
Dependency ranges changed: MonadRandom, array, base, criterion, exceptions, list-t, mtl, simple-effects, text, transformers
API changes (from Hackage documentation)
- Control.Effects: EffectHandler :: ReaderT (EffectWithKind effKind m) m a -> EffectHandler effKind m a
- Control.Effects: EffectWithKind :: (forall method. Effect effKind method Msg -> m (Effect effKind method Res)) -> EffectWithKind effKind m
- Control.Effects: Msg :: MsgOrRes
- Control.Effects: Res :: MsgOrRes
- Control.Effects: [getEffectWithKind] :: EffectWithKind effKind m -> forall method. Effect effKind method Msg -> m (Effect effKind method Res)
- Control.Effects: [unpackEffectHandler] :: EffectHandler effKind m a -> ReaderT (EffectWithKind effKind m) m a
- Control.Effects: data MsgOrRes
- Control.Effects: handleEffect :: (forall method. Effect effKind method Msg -> m (Effect effKind method Res)) -> EffectHandler effKind m a -> m a
- Control.Effects: instance (Control.Effects.MonadEffect method m, Control.Monad.Trans.Class.MonadTrans t, GHC.Base.Monad (t m)) => Control.Effects.MonadEffect method (t m)
- Control.Effects: instance Control.Monad.Base.MonadBase b m => Control.Monad.Base.MonadBase b (Control.Effects.EffectHandler effKind m)
- Control.Effects: instance Control.Monad.Catch.MonadCatch m => Control.Monad.Catch.MonadCatch (Control.Effects.EffectHandler effKind m)
- Control.Effects: instance Control.Monad.Catch.MonadThrow m => Control.Monad.Catch.MonadThrow (Control.Effects.EffectHandler effKind m)
- Control.Effects: instance Control.Monad.IO.Class.MonadIO m => Control.Monad.IO.Class.MonadIO (Control.Effects.EffectHandler effKind m)
- Control.Effects: instance Control.Monad.Random.Class.MonadRandom m => Control.Monad.Random.Class.MonadRandom (Control.Effects.EffectHandler effKind m)
- Control.Effects: instance Control.Monad.Reader.Class.MonadReader s m => Control.Monad.Reader.Class.MonadReader s (Control.Effects.EffectHandler effKind m)
- Control.Effects: instance Control.Monad.Runnable.RunnableTrans (Control.Effects.EffectHandler effKind)
- Control.Effects: instance Control.Monad.State.Class.MonadState s m => Control.Monad.State.Class.MonadState s (Control.Effects.EffectHandler effKind m)
- Control.Effects: instance Control.Monad.Trans.Class.MonadTrans (Control.Effects.EffectHandler effKind)
- Control.Effects: instance Control.Monad.Trans.Control.MonadBaseControl b m => Control.Monad.Trans.Control.MonadBaseControl b (Control.Effects.EffectHandler effKind m)
- Control.Effects: instance GHC.Base.Alternative m => GHC.Base.Alternative (Control.Effects.EffectHandler effKind m)
- Control.Effects: instance GHC.Base.Applicative m => GHC.Base.Applicative (Control.Effects.EffectHandler effKind m)
- Control.Effects: instance GHC.Base.Functor m => GHC.Base.Functor (Control.Effects.EffectHandler effKind m)
- Control.Effects: instance GHC.Base.Monad m => Control.Effects.MonadEffect effKind (Control.Effects.EffectHandler effKind m)
- Control.Effects: instance GHC.Base.Monad m => GHC.Base.Monad (Control.Effects.EffectHandler effKind m)
- Control.Effects: newtype EffectHandler effKind m a
- Control.Effects: newtype EffectWithKind effKind m
- Control.Effects.List: Choose :: Type -> NonDeterministic
- Control.Effects.List: instance GHC.Base.Monad m => Control.Effects.MonadEffect Control.Effects.List.NonDeterministic (ListT.ListT m)
- Control.Effects.List: newtype NonDeterministic
- Control.Effects.Reader: ReadEnv :: ReadEnv e
- Control.Effects.Reader: handleReadEnv :: Functor m => m e -> EffectHandler (ReadEnv e) m a -> m a
- Control.Effects.Reader: handleSubreader :: MonadEffect (ReadEnv e) m => (e -> e') -> EffectHandler (ReadEnv e') m a -> m a
- Control.Effects.Signal: class Throws e m => Handles e m
- Control.Effects.Signal: handleRecursive :: Handles e m => (e -> m a) -> m a -> m a
- Control.Effects.Signal: instance (GHC.Base.Monad m, GHC.Base.Monad (t m), Control.Effects.Signal.Handles e m, Control.Monad.Runnable.RunnableTrans t) => Control.Effects.Signal.Handles e (t m)
- Control.Effects.Signal: instance (GHC.Base.Monad m, b ~ c) => Control.Effects.MonadEffect (Control.Effects.Signal.Signal a c) (Control.Effects.EffectHandler (Control.Effects.Signal.Signal a b) m)
- Control.Effects.Signal: instance GHC.Base.Monad m => Control.Effects.Signal.Handles e (Control.Monad.Trans.Except.ExceptT e m)
- Control.Effects.Signal: type Throws e m = MonadEffect (Signal e Void) m
- Control.Effects.State: Get :: State s
- Control.Effects.State: Set :: State s
- Control.Effects.State: handleState :: forall m s a. Monad m => m s -> (s -> m ()) -> EffectHandler (State s) m a -> m a
- Control.Effects.State: handleStateIO :: MonadIO m => s -> EffectHandler (State s) m a -> m a
- Control.Effects.State: handleStateT :: Monad m => s -> StateT s m a -> m a
+ Control.Effects: RuntimeImplemented :: ReaderT (EffMethods e m) m a -> RuntimeImplemented e m a
+ Control.Effects: [getRuntimeImplemented] :: RuntimeImplemented e m a -> ReaderT (EffMethods e m) m a
+ Control.Effects: class Effect e where {
+ Control.Effects: data family EffMethods e (m :: * -> *) :: *;
+ Control.Effects: implement :: forall e m a. EffMethods e m -> RuntimeImplemented e m a -> m a
+ Control.Effects: instance (Control.Effects.Effect e, GHC.Base.Monad m, Control.Effects.CanLift e (Control.Effects.RuntimeImplemented e)) => Control.Effects.MonadEffect e (Control.Effects.RuntimeImplemented e m)
+ Control.Effects: instance (Control.Effects.MonadEffect e m, GHC.Base.Monad (t m), Control.Effects.CanLift e t) => Control.Effects.MonadEffect e (t m)
+ Control.Effects: instance Control.Monad.Base.MonadBase b m => Control.Monad.Base.MonadBase b (Control.Effects.RuntimeImplemented e m)
+ Control.Effects: instance Control.Monad.Catch.MonadCatch m => Control.Monad.Catch.MonadCatch (Control.Effects.RuntimeImplemented e m)
+ Control.Effects: instance Control.Monad.Catch.MonadThrow m => Control.Monad.Catch.MonadThrow (Control.Effects.RuntimeImplemented e m)
+ Control.Effects: instance Control.Monad.IO.Class.MonadIO m => Control.Monad.IO.Class.MonadIO (Control.Effects.RuntimeImplemented e m)
+ Control.Effects: instance Control.Monad.Random.Class.MonadRandom m => Control.Monad.Random.Class.MonadRandom (Control.Effects.RuntimeImplemented e m)
+ Control.Effects: instance Control.Monad.Reader.Class.MonadReader r m => Control.Monad.Reader.Class.MonadReader r (Control.Effects.RuntimeImplemented e m)
+ Control.Effects: instance Control.Monad.Runnable.RunnableTrans (Control.Effects.RuntimeImplemented e)
+ Control.Effects: instance Control.Monad.State.Class.MonadState s m => Control.Monad.State.Class.MonadState s (Control.Effects.RuntimeImplemented e m)
+ Control.Effects: instance Control.Monad.Trans.Class.MonadTrans (Control.Effects.RuntimeImplemented e)
+ Control.Effects: instance Control.Monad.Trans.Control.MonadBaseControl b m => Control.Monad.Trans.Control.MonadBaseControl b (Control.Effects.RuntimeImplemented e m)
+ Control.Effects: instance GHC.Base.Alternative m => GHC.Base.Alternative (Control.Effects.RuntimeImplemented e m)
+ Control.Effects: instance GHC.Base.Applicative m => GHC.Base.Applicative (Control.Effects.RuntimeImplemented e m)
+ Control.Effects: instance GHC.Base.Functor m => GHC.Base.Functor (Control.Effects.RuntimeImplemented e m)
+ Control.Effects: instance GHC.Base.Monad m => GHC.Base.Monad (Control.Effects.RuntimeImplemented e m)
+ Control.Effects: instance GHC.Base.MonadPlus m => GHC.Base.MonadPlus (Control.Effects.RuntimeImplemented e m)
+ Control.Effects: liftThrough :: forall t m. (Effect e, Generic (EffMethods e m), MonadTrans t, Monad m, Monad (t m), SimpleMethods (EffMethods e) m t) => EffMethods e m -> EffMethods e (t m)
+ Control.Effects: mergeContext :: (Effect e, Generic (EffMethods e m), MonadicMethods (EffMethods e) m) => m (EffMethods e m) -> EffMethods e m
+ Control.Effects: newtype RuntimeImplemented e m a
+ Control.Effects: type CanLift e t = MonadTrans t;
+ Control.Effects: type family CanLift e (t :: (* -> *) -> * -> *) :: Constraint;
+ Control.Effects: }
+ Control.Effects.Async: AsyncThread :: (Async (m a)) -> AsyncThread m a
+ Control.Effects.Async: async :: MonadEffect Async m => m a -> m (AsyncThread m a)
+ Control.Effects.Async: data Async
+ Control.Effects.Async: implementAsyncViaIO :: IO a -> IO a
+ Control.Effects.Async: instance Control.Effects.Effect Control.Effects.Async.Async
+ Control.Effects.Async: instance Control.Effects.MonadEffect Control.Effects.Async.Async GHC.Types.IO
+ Control.Effects.Async: instance GHC.Base.Functor m => GHC.Base.Functor (Control.Effects.Async.AsyncThread m)
+ Control.Effects.Async: instance GHC.Classes.Eq (Control.Effects.Async.AsyncThread m a)
+ Control.Effects.Async: instance GHC.Classes.Ord (Control.Effects.Async.AsyncThread m a)
+ Control.Effects.Async: newtype AsyncThread m a
+ Control.Effects.Async: waitAsync :: MonadEffect Async m => AsyncThread m a -> m a
+ Control.Effects.Early: instance Control.Effects.Effect (Control.Effects.Early.Early a)
+ Control.Effects.Generic: class IndependentOfM (a :: k) (m :: * -> *)
+ Control.Effects.Generic: class Monad m => MonadicMethod a f fM m
+ Control.Effects.Generic: class MonadicMethods a m
+ Control.Effects.Generic: class ProductOfMonadicMethods p pM a m
+ Control.Effects.Generic: class ProductOfSimpleMethods p pM m t where {
+ Control.Effects.Generic: class (MonadTrans t, Monad m) => SimpleMethod f fM (m :: * -> *) (t :: (* -> *) -> * -> *) where {
+ Control.Effects.Generic: class (Generic (a m), Generic (a (t m)), Generic (a M)) => SimpleMethods a m t
+ Control.Effects.Generic: data M a
+ Control.Effects.Generic: genericLiftThrough :: forall t e em m. (MonadTrans t, Monad m, Monad (t m), SimpleMethods (em e) m t) => em e m -> em e (t m)
+ Control.Effects.Generic: genericMergeContext :: MonadicMethods a m => m (a m) -> a m
+ Control.Effects.Generic: instance (Control.Monad.Trans.Class.MonadTrans t, GHC.Base.Monad m, a ~ m x) => Control.Effects.Generic.SimpleMethod a (Control.Effects.Generic.M x) m t
+ Control.Effects.Generic: instance (GHC.Generics.Rep (a m) ~ GHC.Generics.D1 m1 (GHC.Generics.C1 m2 p), GHC.Generics.Rep (a Control.Effects.Generic.M) ~ GHC.Generics.D1 m1 (GHC.Generics.C1 m2 pM), Control.Effects.Generic.ProductOfMonadicMethods p pM a m, GHC.Generics.Generic (a m), GHC.Generics.Generic (a Control.Effects.Generic.M)) => Control.Effects.Generic.MonadicMethods a m
+ Control.Effects.Generic: instance (TypeError ...) => Control.Effects.Generic.IndependentOfM Control.Effects.Generic.M m
+ Control.Effects.Generic: instance (b ~ m x, GHC.Base.Monad m) => Control.Effects.Generic.MonadicMethod a b (Control.Effects.Generic.M x) m
+ Control.Effects.Generic: instance (f ~ (a -> b), Control.Effects.Generic.SimpleMethod b bM m t, Control.Effects.Generic.IndependentOfM a m) => Control.Effects.Generic.SimpleMethod f (a -> bM) m t
+ Control.Effects.Generic: instance (f ~ (b -> c), GHC.Base.Monad m, Control.Effects.Generic.MonadicMethod a c cM m) => Control.Effects.Generic.MonadicMethod a f (bM -> cM) m
+ Control.Effects.Generic: instance Control.Effects.Generic.MonadicMethod a f fM m => Control.Effects.Generic.ProductOfMonadicMethods (GHC.Generics.S1 m1 (GHC.Generics.Rec0 f)) (GHC.Generics.S1 m1 (GHC.Generics.Rec0 fM)) a m
+ Control.Effects.Generic: instance Control.Effects.Generic.SimpleMethod f fM m t => Control.Effects.Generic.ProductOfSimpleMethods (GHC.Generics.S1 m1 (GHC.Generics.Rec0 f)) (GHC.Generics.S1 m1 (GHC.Generics.Rec0 fM)) m t
+ Control.Effects.Generic: instance forall k (a :: (k -> *) -> *) (m :: k -> *) (m1 :: GHC.Generics.Meta) (m2 :: GHC.Generics.Meta) (p :: * -> *) (pM :: * -> *) (t :: (k -> *) -> k -> *). (GHC.Generics.Rep (a m) ~ GHC.Generics.D1 m1 (GHC.Generics.C1 m2 p), GHC.Generics.Rep (a Control.Effects.Generic.M) ~ GHC.Generics.D1 m1 (GHC.Generics.C1 m2 pM), GHC.Generics.Rep (a (t m)) ~ GHC.Generics.D1 m1 (GHC.Generics.C1 m2 (Control.Effects.Generic.LiftedProducts p pM m t)), Control.Effects.Generic.ProductOfSimpleMethods p pM m t, GHC.Generics.Generic (a m), GHC.Generics.Generic (a (t m)), GHC.Generics.Generic (a Control.Effects.Generic.M)) => Control.Effects.Generic.SimpleMethods a m t
+ Control.Effects.Generic: instance forall k (a :: k) (m :: * -> *). Control.Effects.Generic.IndependentOfM a m
+ Control.Effects.Generic: instance forall k (m :: * -> *) (a :: (* -> *) -> *) f (fM :: k). ((TypeError ...), GHC.Base.Monad m) => Control.Effects.Generic.MonadicMethod a f fM m
+ Control.Effects.Generic: instance forall k (m :: * -> *) (t :: (* -> *) -> * -> *) a (b :: k). ((TypeError ...), GHC.Base.Monad m, Control.Monad.Trans.Class.MonadTrans t) => Control.Effects.Generic.SimpleMethod a b m t
+ Control.Effects.Generic: instance forall k1 k2 (a :: k1 -> k2) (m :: * -> *) (b :: k1). (Control.Effects.Generic.IndependentOfM a m, Control.Effects.Generic.IndependentOfM b m) => Control.Effects.Generic.IndependentOfM (a b) m
+ Control.Effects.Generic: instance forall k1 k2 (f1 :: k2 -> *) (f1M :: k1 -> *) (a :: (* -> *) -> *) (m :: * -> *) (f2 :: k2 -> *) (f2M :: k1 -> *). (Control.Effects.Generic.ProductOfMonadicMethods f1 f1M a m, Control.Effects.Generic.ProductOfMonadicMethods f2 f2M a m) => Control.Effects.Generic.ProductOfMonadicMethods (f1 GHC.Generics.:*: f2) (f1M GHC.Generics.:*: f2M) a m
+ Control.Effects.Generic: instance forall k1 k2 k3 (f1 :: * -> *) (f1M :: k3 -> *) (m :: k2) (t :: k1) (f2 :: * -> *) (f2M :: k3 -> *). (Control.Effects.Generic.ProductOfSimpleMethods f1 f1M m t, Control.Effects.Generic.ProductOfSimpleMethods f2 f2M m t) => Control.Effects.Generic.ProductOfSimpleMethods (f1 GHC.Generics.:*: f2) (f1M GHC.Generics.:*: f2M) m t
+ Control.Effects.Generic: liftMethod :: SimpleMethod f fM m t => Proxy m -> Proxy t -> Proxy fM -> f -> LiftedMethod f fM m t
+ Control.Effects.Generic: liftProducts :: ProductOfSimpleMethods p pM m t => Proxy m -> Proxy t -> Proxy pM -> p x -> LiftedProducts p pM m t x
+ Control.Effects.Generic: liftSimple :: SimpleMethods a m t => a m -> a (t m)
+ Control.Effects.Generic: mergeMethod :: MonadicMethod a f fM m => Proxy fM -> (a m -> f) -> m (a m) -> f
+ Control.Effects.Generic: mergeMonadicMethods :: MonadicMethods a m => m (a m) -> a m
+ Control.Effects.Generic: mergeMonadicProducts :: ProductOfMonadicMethods p pM a m => Proxy p -> Proxy pM -> m (a m) -> (a m -> p x) -> p x
+ Control.Effects.Generic: type family LiftedMethod f fM m t;
+ Control.Effects.Generic: }
+ Control.Effects.List: data NonDeterminism
+ Control.Effects.List: evaluateAll :: Monad m => ListT m a -> m ()
+ Control.Effects.List: instance Control.Effects.Effect Control.Effects.List.NonDeterminism
+ Control.Effects.List: instance GHC.Base.Monad m => Control.Effects.MonadEffect Control.Effects.List.NonDeterminism (ListT.ListT m)
+ Control.Effects.Reader: implementReadEnv :: Functor m => m e -> RuntimeImplemented (ReadEnv e) m a -> m a
+ Control.Effects.Reader: instance Control.Effects.Effect (Control.Effects.Reader.ReadEnv e)
+ Control.Effects.Reader: instance GHC.Generics.Generic (Control.Effects.EffMethods (Control.Effects.Reader.ReadEnv e) m)
+ Control.Effects.Signal: data HandleException e
+ Control.Effects.Signal: handleWithoutDiscarding :: forall e m a. MonadEffect (HandleException e) m => (e -> m a) -> m a -> m a
+ Control.Effects.Signal: instance (GHC.Base.Monad m, b ~ c) => Control.Effects.MonadEffect (Control.Effects.Signal.Signal a c) (Control.Effects.RuntimeImplemented (Control.Effects.Signal.Signal a b) m)
+ Control.Effects.Signal: instance Control.Effects.Effect (Control.Effects.Signal.HandleException e)
+ Control.Effects.Signal: instance Control.Effects.Effect (Control.Effects.Signal.Signal a b)
+ Control.Effects.Signal: instance GHC.Base.Monad m => Control.Effects.MonadEffect (Control.Effects.Signal.HandleException e) (Control.Monad.Trans.Except.ExceptT e m)
+ Control.Effects.Signal: instance GHC.Generics.Generic (Control.Effects.EffMethods (Control.Effects.Signal.Signal a b) m)
+ Control.Effects.Signal: type Throw e = Signal e Void
+ Control.Effects.State: implementStateViaIORef :: forall s m a. MonadIO m => s -> RuntimeImplemented (State s) m a -> m a
+ Control.Effects.State: implementStateViaStateT :: forall s m a. Monad m => s -> StateT s m a -> m a
+ Control.Effects.State: instance Control.Effects.Effect (Control.Effects.State.State s)
+ Control.Effects.State: instance GHC.Generics.Generic (Control.Effects.EffMethods (Control.Effects.State.State s) m)
+ Control.Monad.Runnable: class Runnable m => PureRunnable m
+ Control.Monad.Runnable: instance (Control.Monad.Runnable.PureRunnable m, Control.Monad.Runnable.RunnableTrans t, GHC.Base.Monad (t m)) => Control.Monad.Runnable.PureRunnable (t m)
+ Control.Monad.Runnable: instance Control.Monad.Runnable.PureRunnable Data.Functor.Identity.Identity
+ Control.Monad.Runnable: runPureMonad :: PureRunnable m => MonadicState m -> m a -> MonadicResult m a
- Control.Effects: class Monad m => MonadEffect effKind m
+ Control.Effects: class (Effect e, Monad m) => MonadEffect e m
- Control.Effects: effect :: MonadEffect effKind m => Effect effKind method Msg -> m (Effect effKind method Res)
+ Control.Effects: effect :: MonadEffect e m => EffMethods e m
- Control.Effects.List: choose :: MonadEffect NonDeterministic m => [a] -> m a
+ Control.Effects.List: choose :: forall a m. MonadEffect NonDeterminism m => [a] -> m a
- Control.Effects.List: deadEnd :: MonadEffect NonDeterministic m => m a
+ Control.Effects.List: deadEnd :: MonadEffect NonDeterminism m => m a
- Control.Effects.Signal: MaybeT :: m (Maybe a) -> MaybeT a
+ Control.Effects.Signal: MaybeT :: m Maybe a -> MaybeT a
- Control.Effects.Signal: [runMaybeT] :: MaybeT a -> m (Maybe a)
+ Control.Effects.Signal: [runMaybeT] :: MaybeT a -> m Maybe a
- Control.Effects.Signal: handleException :: Monad m => (a -> m c) -> ExceptT a m c -> m c
+ Control.Effects.Signal: handleException :: forall a c m. Monad m => (a -> m c) -> ExceptT a m c -> m c
- Control.Effects.Signal: handleSignal :: forall a b c m. Monad m => (a -> m (ResumeOrBreak b c)) -> EffectHandler (Signal a b) (ExceptT c m) c -> m c
+ Control.Effects.Signal: handleSignal :: forall a b c m. Monad m => (a -> m (ResumeOrBreak b c)) -> RuntimeImplemented (Signal a b) (ExceptT c m) c -> m c
- Control.Effects.Signal: handleToEither :: ExceptT e m a -> m (Either e a)
+ Control.Effects.Signal: handleToEither :: forall e a m. ExceptT e m a -> m (Either e a)
- Control.Effects.Signal: handleToEitherRecursive :: Handles e m => m a -> m (Either e a)
+ Control.Effects.Signal: handleToEitherRecursive :: MonadEffect (HandleException e) m => m a -> m (Either e a)
- Control.Effects.Signal: signal :: MonadEffect (Signal a b) m => a -> m b
+ Control.Effects.Signal: signal :: forall a b m. MonadEffect (Signal a b) m => a -> m b
- Control.Effects.Signal: throwSignal :: Throws a m => a -> m b
+ Control.Effects.Signal: throwSignal :: MonadEffect (Throw a) m => a -> m b
- Control.Monad.Runnable: class Monad m => Runnable m where type MonadicState m :: * type MonadicResult m a :: * where {
+ Control.Monad.Runnable: class Monad m => Runnable m where {
- Control.Monad.Runnable: class MonadTrans t => RunnableTrans t where type TransformerState t (m :: * -> *) :: * type TransformerResult t (m :: * -> *) a :: * where {
+ Control.Monad.Runnable: class MonadTrans t => RunnableTrans t where {
- Control.Monad.Runnable: runMonad :: Runnable m => MonadicState m -> m a -> IO (MonadicResult m a)
+ Control.Monad.Runnable: runMonad :: (Runnable m, PureRunnable m) => MonadicState m -> m a -> IO (MonadicResult m a)
Files
- bench/Bench.hs +15/−8
- simple-effects.cabal +66/−63
- src/Control/Effects.hs +60/−49
- src/Control/Effects/Async.hs +86/−0
- src/Control/Effects/Early.hs +14/−9
- src/Control/Effects/Generic.hs +136/−0
- src/Control/Effects/List.hs +20/−12
- src/Control/Effects/Reader.hs +17/−11
- src/Control/Effects/Signal.hs +85/−75
- src/Control/Effects/State.hs +46/−39
- src/Control/Monad/Runnable.hs +16/−5
- src/Tutorial/T1_Introduction.hs +234/−0
- src/Tutorial/T2_Details.hs +288/−0
- src/Tutorial/T3_CustomEffects.hs +419/−0
- src/Tutorial/Test.hs +113/−0
- test/Main.hs +60/−6
bench/Bench.hs view
@@ -1,26 +1,33 @@ {-# LANGUAGE FlexibleContexts, TypeApplications #-} - +{-# OPTIONS_GHC -ddump-simpl #-} import GHC.IO.Encoding (setLocaleEncoding, utf8) -import Control.Effects.State - +import qualified Control.Effects.State as Eff1 import Control.Monad.State.Strict import Control.Monad.Trans.State.Strict (execStateT) import Criterion.Main +modify'' :: MonadState s m => (s -> s) -> m () +modify'' f = do + s <- get + let s' = f s + s' `seq` put s' +{-# INLINE modify'' #-} + mtlSimple :: MonadState Int m => m () mtlSimple = replicateM_ 1000000 mtlSimple' - where mtlSimple' = modify' (+ 1) + where mtlSimple' = modify'' (+ 1) -effectsSimple :: (MonadEffectState Int m) => m () +effectsSimple :: (Eff1.MonadEffect (Eff1.State Int) m) => m () effectsSimple = replicateM_ 1000000 effectsSimple' - where effectsSimple' = modifyState (+ (1 :: Int)) + where effectsSimple' = Eff1.modifyState (+ (1 :: Int)) main :: IO () main = do setLocaleEncoding utf8 defaultMain [ bench "MTL state" $ nfIO (execStateT mtlSimple 0) - , bench "Effects state" $ nfIO effState + , bench "Effects state" $ nfIO effState1 ] - where effState = handleStateT (0 :: Int) (effectsSimple >> getState @Int) + where + effState1 = Eff1.handleStateT (0 :: Int) (effectsSimple >> Eff1.getState @Int)
simple-effects.cabal view
@@ -1,63 +1,66 @@-name: simple-effects-version: 0.9.0.1-cabal-version: >=1.10-build-type: Simple-license: BSD3-license-file: LICENSE-copyright: 2016 Luka Horvat-maintainer: luka.horvat9@gmail.com-homepage: https://gitlab.com/LukaHorvat/simple-effects-synopsis: A simple effect system that integrates with MTL-description:- Please see README.md-category: Control-author: Luka Horvat--library- exposed-modules:- Control.Effects- Control.Effects.State- Control.Effects.Reader- Control.Effects.List- Control.Effects.Signal- Control.Effects.Early- Control.Effects.Parallel- Control.Monad.Runnable- build-depends:- base >=4.7 && <5,- transformers >=0.5.2.0,- mtl >=2.2.1,- monad-control ==1.0.*,- transformers-base ==0.4.*,- list-t >=1,- array >=0.5.1.1,- MonadRandom >=0.5.1,- exceptions >=0.8.3,- text >=1.2.2.1- default-language: Haskell2010- hs-source-dirs: src- other-modules:- Import- ghc-options: -Wall--test-suite tests- type: exitcode-stdio-1.0- main-is: Main.hs- build-depends:- base >=4.7 && <5,- simple-effects >=0.9.0.1- default-language: Haskell2010- hs-source-dirs: test- ghc-options: -Wall -threaded -with-rtsopts=-N--benchmark bench-effects- type: exitcode-stdio-1.0- main-is: Bench.hs- build-depends:- base >=4.9.1.0,- criterion >=1.1.4.0,- mtl >=2.2.1,- transformers >=0.5.2.0,- simple-effects >=0.9.0.1- default-language: Haskell2010- hs-source-dirs: bench+name: simple-effects +version: 0.10.0.0 +synopsis: A simple effect system that integrates with MTL +description: Please see the tutorial modules +homepage: https://gitlab.com/LukaHorvat/simple-effects +license: BSD3 +license-file: LICENSE +author: Luka Horvat +maintainer: luka.horvat9@gmail.com +copyright: 2018 Luka Horvat +category: Control +build-type: Simple +cabal-version: >=1.10 + +library + exposed-modules: Control.Effects + , Control.Effects.State + , Control.Effects.Reader + , Control.Effects.List + , Control.Effects.Signal + , Control.Effects.Early + , Control.Effects.Parallel + , Control.Effects.Generic + , Control.Effects.Async + , Control.Monad.Runnable + , Tutorial.T1_Introduction + , Tutorial.T2_Details + , Tutorial.T3_CustomEffects + other-modules: Import + Tutorial.Test + hs-source-dirs: src + default-language: Haskell2010 + build-depends: base >= 4.7 && < 5 + , transformers + , mtl + , monad-control == 1.0.* + , transformers-base == 0.4.* + , list-t + , array + , MonadRandom + , exceptions + , text + , bytestring + , async + ghc-options: -Wall -O2 + +test-suite tests + hs-source-dirs: test + main-is: Main.hs + default-language: Haskell2010 + type: exitcode-stdio-1.0 + build-depends: base >= 4.7 && < 5 + , simple-effects + ghc-options: -Wall -threaded -with-rtsopts=-N + +benchmark bench-effects + type: exitcode-stdio-1.0 + hs-source-dirs: bench + main-is: Bench.hs + build-depends: base + , criterion + , mtl + , transformers + , simple-effects + default-language: Haskell2010 + ghc-options: -O2
src/Control/Effects.hs view
@@ -1,65 +1,76 @@-{-# LANGUAGE TypeFamilies, MultiParamTypeClasses, FlexibleInstances, DeriveFunctor - , GeneralizedNewtypeDeriving, UndecidableInstances, StandaloneDeriving - , IncoherentInstances #-} -{-# LANGUAGE DataKinds, PolyKinds, TypeInType, Rank2Types, TypeOperators, ConstraintKinds #-} +{-# LANGUAGE TypeFamilies, RankNTypes #-} +{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, GeneralizedNewtypeDeriving #-} +{-# LANGUAGE FlexibleContexts, ScopedTypeVariables, InstanceSigs, UndecidableInstances #-} +{-# LANGUAGE DefaultSignatures #-} +{-# LANGUAGE StandaloneDeriving, DataKinds #-} +{-# LANGUAGE TypeOperators #-} module Control.Effects (module Control.Effects) where -import Import +import Import hiding (liftThrough) import Control.Monad.Runnable -import Data.Kind - -data MsgOrRes = Msg | Res -data family Effect (effKind :: Type) :: effKind -> MsgOrRes -> Type - -class Monad m => MonadEffect effKind m where - -- | Use the effect described by 'method'. - effect :: Effect effKind method 'Msg -> m (Effect effKind method 'Res) +import Control.Effects.Generic +import GHC.Generics -newtype EffectWithKind effKind m = EffectWithKind - { getEffectWithKind :: forall method. Effect effKind method 'Msg -> m (Effect effKind method 'Res) } +class Effect e where + data EffMethods e (m :: * -> *) :: * + type CanLift e (t :: (* -> *) -> * -> *) :: Constraint + type CanLift e t = MonadTrans t + liftThrough :: + forall t m. (CanLift e t, Monad m, Monad (t m)) + => EffMethods e m -> EffMethods e (t m) + default liftThrough :: + forall t m. + ( Generic (EffMethods e m), MonadTrans t, Monad m, Monad (t m) + , SimpleMethods (EffMethods e) m t ) + => EffMethods e m -> EffMethods e (t m) + liftThrough = genericLiftThrough + + mergeContext :: Monad m => m (EffMethods e m) -> EffMethods e m + default mergeContext :: + (Generic (EffMethods e m), MonadicMethods (EffMethods e) m) + => m (EffMethods e m) -> EffMethods e m + mergeContext = genericMergeContext --- | The 'EffectHandler' is really just a 'ReaderT' carrying around the function that knows how to --- handle the effect. -newtype EffectHandler effKind m a = EffectHandler - { unpackEffectHandler :: ReaderT (EffectWithKind effKind m) m a } - deriving ( Functor, Applicative, Monad, Alternative, MonadState s, MonadIO, MonadCatch - , MonadThrow, MonadRandom ) +class (Effect e, Monad m) => MonadEffect e m where + effect :: EffMethods e m -instance MonadTrans (EffectHandler effKind) where - lift = EffectHandler . lift +instance {-# OVERLAPPABLE #-} + (MonadEffect e m, Monad (t m), CanLift e t) + => MonadEffect e (t m) where + effect = liftThrough effect -instance RunnableTrans (EffectHandler effKind) where - type TransformerState (EffectHandler effKind) m = EffectWithKind effKind m - type TransformerResult (EffectHandler effKind) m a = a - currentTransState = EffectHandler ask - restoreTransState = return - runTransformer m = runReaderT (unpackEffectHandler m) +newtype RuntimeImplemented e m a = RuntimeImplemented + { getRuntimeImplemented :: ReaderT (EffMethods e m) m a } + deriving + (Functor, Applicative, Monad, MonadPlus, Alternative, MonadState s, MonadIO, MonadCatch + , MonadThrow, MonadRandom ) -instance MonadReader s m => MonadReader s (EffectHandler effKind m) where - ask = EffectHandler (lift ask) - local f (EffectHandler rdr) = EffectHandler (ReaderT $ local f . runReaderT rdr) +instance MonadTrans (RuntimeImplemented e) where + lift = RuntimeImplemented . lift -deriving instance MonadBase b m => MonadBase b (EffectHandler effKind m) +instance MonadReader r m => MonadReader r (RuntimeImplemented e m) where + ask = RuntimeImplemented (lift ask) + local f (RuntimeImplemented rdr) = RuntimeImplemented (ReaderT (local f . runReaderT rdr)) -instance MonadBaseControl b m => MonadBaseControl b (EffectHandler effKind m) where - type StM (EffectHandler effKind m) a = StM (ReaderT (EffectWithKind effKind m) m) a - liftBaseWith f = EffectHandler $ liftBaseWith $ \q -> f (q . unpackEffectHandler) - restoreM = EffectHandler . restoreM +deriving instance MonadBase b m => MonadBase b (RuntimeImplemented e m) +instance MonadBaseControl b m => MonadBaseControl b (RuntimeImplemented e m) where + type StM (RuntimeImplemented e m) a = StM (ReaderT (EffMethods e m) m) a + liftBaseWith f = RuntimeImplemented $ liftBaseWith $ \q -> f (q . getRuntimeImplemented) + restoreM = RuntimeImplemented . restoreM -instance {-# OVERLAPPABLE #-} (MonadEffect method m, MonadTrans t, Monad (t m)) - => MonadEffect method (t m) where - {-# INLINE effect #-} - effect msg = lift (effect msg) +instance RunnableTrans (RuntimeImplemented e) where + type TransformerResult (RuntimeImplemented e) m a = a + type TransformerState (RuntimeImplemented e) m = EffMethods e m + currentTransState = RuntimeImplemented ask + restoreTransState = return + runTransformer (RuntimeImplemented m) = runReaderT m -instance Monad m => MonadEffect effKind (EffectHandler effKind m) where - {-# INLINE effect #-} - effect msg = EffectHandler (ReaderT (($ msg) . getEffectWithKind)) +instance (Effect e, Monad m, CanLift e (RuntimeImplemented e)) + => MonadEffect e (RuntimeImplemented e m) where + effect = mergeContext $ RuntimeImplemented (liftThrough <$> ask) --- | Handle the effect described by 'effKind'. -handleEffect :: - (forall method. Effect effKind method 'Msg -> m (Effect effKind method 'Res)) - -> EffectHandler effKind m a -> m a -handleEffect f eh = runReaderT (unpackEffectHandler eh) (EffectWithKind f) +implement :: forall e m a. EffMethods e m -> RuntimeImplemented e m a -> m a +implement em (RuntimeImplemented r) = runReaderT r em type family MonadEffects effs m :: Constraint where MonadEffects '[] m = ()
+ src/Control/Effects/Async.hs view
@@ -0,0 +1,86 @@+{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE NoMonomorphismRestriction #-} +{-# LANGUAGE DeriveFunctor #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-| The 'Async' effect allows you to fork new threads in monads other than just 'IO'. +-} +module Control.Effects.Async where + +import Import +import Control.Effects +import qualified Control.Concurrent.Async as Async +import Control.Monad.Runnable +import Control.Effects.State + +-- | The name of the effect +data Async + +-- | The type that represents the forked computation in the monad @m@ that eventually computes +-- a value of type @a@. Depending on the monad, the computation may produce zero, one or even +-- multiple values of that type. +newtype AsyncThread m a = AsyncThread (Async.Async (m a)) + deriving (Functor, Eq, Ord) + +instance Effect Async where + data EffMethods Async m = AsyncMethods + { _async :: forall a. m a -> m (AsyncThread m a) + , _waitAsync :: forall a. AsyncThread m a -> m a } + type CanLift Async t = RunnableTrans t + mergeContext mm = AsyncMethods + (\a -> mm >>= ($ a) . _async) + (\a -> mm >>= ($ a) . _waitAsync) + liftThrough (AsyncMethods f g) = AsyncMethods + (\tma -> do + st <- currentTransState + !res <- lift (f (runTransformer tma st)) + return $ mapAsync (lift >=> restoreTransState) res + ) + (\a -> do + st <- currentTransState + res <- lift (g (mapAsync (`runTransformer` st) a)) + restoreTransState res + ) + where + mapAsync :: (m a -> n b) -> AsyncThread m a -> AsyncThread n b + mapAsync f' (AsyncThread as) = AsyncThread (fmap f' as) + +-- | The 'IO' implementation uses the @async@ library. +instance MonadEffect Async IO where + effect = AsyncMethods + (fmap (AsyncThread . fmap return) . Async.async) + (\(AsyncThread as) -> join (Async.wait as)) + +-- | Fork a new thread to run the given computation. The monadic context is forked into the new +-- thread. +-- +-- For example, if we use state, the current state value will be visible int he forked computation. +-- Depending on how we ultimately implement the state, modifying it may or may not be visible +-- from the main thread. If we use 'implementStateViaStateT' then setting the state in the forked +-- thread will just modify the thread-local value. On the other hand, if we use +-- 'implementStateViaIORef' then both the main thread and the new thread will use the same reference +-- meaning they can interact through it. +async :: MonadEffect Async m => m a -> m (AsyncThread m a) + +-- | Wait for the thread to finish and return it's result. The monadic context will also be merged. +-- +-- Example: +-- +-- @ +-- 'setState' \@Int 1 +-- th <- 'async' $ do +-- 'setState' \@Int 2 +-- 'waitAsync' th +-- print =<< 'getState' \@Int -- Outputs 2 +-- @ +waitAsync :: MonadEffect Async m => AsyncThread m a -> m a +AsyncMethods async waitAsync = effect + +-- | This will discard the @'MonadEffect' 'Async' m@ constraint by forcing @m@ to be 'IO'. +-- The functions doesn't actually do anything, the real implementation is given by the +-- @'MonadEffect' 'Async' IO@ instance which uses the @async@ package. +implementAsyncViaIO :: IO a -> IO a +implementAsyncViaIO = id
src/Control/Effects/Early.hs view
@@ -1,5 +1,6 @@ {-# LANGUAGE RankNTypes, TypeFamilies, FlexibleContexts, ScopedTypeVariables, MultiParamTypeClasses #-} {-# LANGUAGE FlexibleInstances, DataKinds, GADTs #-} +{-# LANGUAGE NoMonomorphismRestriction #-} -- | A neat effect that you can use to get early returns in your functions. Here's how to use it. -- -- Before: @@ -27,9 +28,9 @@ -- return (x <> y) -- @ -- --- You can use the 'earlyReturn' function directily, or one of the helpers for common use cases. +-- You can use the 'earlyReturn' function directly, or one of the helpers for common use cases. module Control.Effects.Early - ( module Control.Effects, Early + ( module Control.Effects, Early, EffMethods(..) , earlyReturn, handleEarly, onlyDo, ifNothingEarlyReturn, ifNothingDo , ifLeftEarlyReturn, ifLeftDo ) where @@ -38,18 +39,22 @@ import Control.Effects newtype EarlyValue a = EarlyValue { getEarlyValue :: a } -data Early a = Early -data instance Effect (Early a) method mr where - EarlyMsg :: a -> Effect (Early a) 'Early 'Msg - EarlyRes :: { getEarlyRes :: Void } -> Effect (Early a) 'Early 'Res - +data Early a +instance Effect (Early a) where + data EffMethods (Early a) m = EarlyMethods + { _earlyReturn :: forall b. a -> m b } + liftThrough (EarlyMethods f) = EarlyMethods (lift . f) + mergeContext m = EarlyMethods (\a -> do + f <- _earlyReturn <$> m + f a) + instance (Monad m, a ~ b) => MonadEffect (Early a) (ExceptT (EarlyValue b) m) where - effect (EarlyMsg a) = EarlyRes <$> throwE (EarlyValue a) + effect = EarlyMethods (throwE . EarlyValue) -- | Allows you to return early from a function. Make sure you 'handleEarly' to get the actual -- result out. earlyReturn :: forall a b m. MonadEffect (Early a) m => a -> m b -earlyReturn a = fmap (getEarlyValue . absurd . getEarlyRes) . effect $ EarlyMsg a +EarlyMethods earlyReturn = effect -- | Get the result from a computation. Either the early returned one, or the regular result. handleEarly :: Monad m => ExceptT (EarlyValue a) m a -> m a
+ src/Control/Effects/Generic.hs view
@@ -0,0 +1,136 @@+{-# LANGUAGE ScopedTypeVariables, TypeFamilies, PolyKinds, FlexibleInstances #-} +{-# LANGUAGE UndecidableInstances, TypeOperators, MultiParamTypeClasses #-} +{-# LANGUAGE TypeApplications, RankNTypes, DataKinds, ViewPatterns #-} +{-# OPTIONS_GHC -fno-warn-missing-methods #-} +module Control.Effects.Generic where + +import qualified GHC.Generics as Gen +import GHC.Generics +import Control.Monad.Trans +import Data.Proxy +import GHC.TypeLits + +data M a + +class (Generic (a m), Generic (a (t m)), Generic (a M)) => SimpleMethods a m t where + liftSimple :: a m -> a (t m) + +instance + ( Rep (a m) ~ D1 m1 (C1 m2 p) + , Rep (a M) ~ D1 m1 (C1 m2 pM) + , Rep (a (t m)) ~ D1 m1 (C1 m2 (LiftedProducts p pM m t)) + , ProductOfSimpleMethods p pM m t + , Generic (a m), Generic (a (t m)), Generic (a M) ) + => SimpleMethods a m t where + liftSimple a = case Gen.from a of + M1 (M1 p) -> Gen.to (M1 (M1 (liftProducts (Proxy @m) (Proxy @t) (Proxy @pM) p))) + {-# INLINE liftSimple #-} + +class ProductOfSimpleMethods p pM m t where + type LiftedProducts p pM m t :: * -> * + liftProducts :: Proxy m -> Proxy t -> Proxy pM -> p x -> LiftedProducts p pM m t x + +instance SimpleMethod f fM m t => ProductOfSimpleMethods (S1 m1 (Rec0 f)) (S1 m1 (Rec0 fM)) m t where + type LiftedProducts (S1 m1 (Rec0 f)) (S1 m1 (Rec0 fM)) m t = + (S1 m1 (Rec0 (LiftedMethod f fM m t))) + liftProducts p1 p2 _ (M1 (K1 f)) = M1 (K1 (liftMethod p1 p2 (Proxy @fM) f)) + {-# INLINE liftProducts #-} +instance + (ProductOfSimpleMethods f1 f1M m t, ProductOfSimpleMethods f2 f2M m t) + => ProductOfSimpleMethods (f1 :*: f2) (f1M :*: f2M) m t where + type LiftedProducts (f1 :*: f2) (f1M :*: f2M) m t = + LiftedProducts f1 f1M m t :*: LiftedProducts f2 f2M m t + liftProducts p1 p2 _ (f1 :*: f2) = + liftProducts p1 p2 (Proxy @f1M) f1 :*: liftProducts p1 p2 (Proxy @f2M) f2 + {-# INLINE liftProducts #-} + +class (MonadTrans t, Monad m) => SimpleMethod f fM (m :: * -> *) (t :: (* -> *) -> * -> *) where + type LiftedMethod f fM m t + liftMethod :: Proxy m -> Proxy t -> Proxy fM -> f -> LiftedMethod f fM m t +instance (MonadTrans t, Monad m, a ~ m x) => SimpleMethod a (M x) m t where + type LiftedMethod a (M x) m t = t m x + liftMethod _ _ _ = lift @t + {-# INLINE liftMethod #-} +type family FuncRes f where + FuncRes (a -> b) = b +instance (f ~ (a -> b), SimpleMethod b bM m t, IndependentOfM a m) => SimpleMethod f (a -> bM) m t where + type LiftedMethod f (a -> bM) m t = a -> LiftedMethod (FuncRes f) bM m t + liftMethod p1 p2 _ f a = liftMethod p1 p2 (Proxy @bM) (f a :: b) + {-# INLINE liftMethod #-} +instance {-# OVERLAPPABLE #-} + ( TypeError ('Text "Effect methods must be monadic actions or functions resulting in monadic actions") + , Monad m, MonadTrans t ) + => SimpleMethod a b m t + +class IndependentOfM (a :: k) (m :: * -> *) where +instance + (IndependentOfM a m, IndependentOfM b m) + => IndependentOfM (a b) m +instance + TypeError + ('Text "Parameters of methods can't depend on the monadic context (" + ':<>: 'ShowType m + ':<>: 'Text ")") + => IndependentOfM M m +instance {-# OVERLAPPABLE #-} + IndependentOfM (a :: k) m + +genericLiftThrough :: + forall t e em m. (MonadTrans t, Monad m, Monad (t m), SimpleMethods (em e) m t) + => em e m -> em e (t m) +genericLiftThrough = liftSimple +{-# INLINE genericLiftThrough #-} + + + +class MonadicMethods a m where + mergeMonadicMethods :: m (a m) -> a m +instance + ( Rep (a m) ~ D1 m1 (C1 m2 p) + , Rep (a M) ~ D1 m1 (C1 m2 pM) + , ProductOfMonadicMethods p pM a m + , Generic (a m), Generic (a M) ) + => MonadicMethods a m where + mergeMonadicMethods a = Gen.to (M1 (M1 (mergeMonadicProducts (Proxy @p) (Proxy @pM) a f))) + where + f (Gen.from -> M1 (M1 p)) = p + {-# INLINE mergeMonadicMethods #-} + +class ProductOfMonadicMethods p pM a m where + mergeMonadicProducts :: Proxy p -> Proxy pM -> m (a m) -> (a m -> p x) -> p x +instance MonadicMethod a f fM m => ProductOfMonadicMethods (S1 m1 (Rec0 f)) (S1 m1 (Rec0 fM)) a m where + mergeMonadicProducts _ _ ma f = M1 (K1 (mergeMethod (Proxy @fM) (g . f) ma)) + where + g (M1 (K1 x)) = x + {-# INLINE mergeMonadicProducts #-} +instance + (ProductOfMonadicMethods f1 f1M a m, ProductOfMonadicMethods f2 f2M a m) + => ProductOfMonadicMethods (f1 :*: f2) (f1M :*: f2M) a m where + mergeMonadicProducts _ _ ma f = + mergeMonadicProducts (Proxy @f1) (Proxy @f1M) ma (g1 . f) + :*: mergeMonadicProducts (Proxy @f2) (Proxy @f2M) ma (g2 . f) + where + g1 (x :*: _) = x + g2 (_ :*: x) = x + {-# INLINE mergeMonadicProducts #-} + +class Monad m => MonadicMethod a f fM m where + mergeMethod :: Proxy fM -> (a m -> f) -> m (a m) -> f +instance (b ~ m x, Monad m) => MonadicMethod a b (M x) m where + mergeMethod _ f ma = do + a <- ma + f a + {-# INLINE mergeMethod #-} +instance (f ~ (b -> c), Monad m, MonadicMethod a c cM m) => MonadicMethod a f (bM -> cM) m where + mergeMethod _ f ma b = mergeMethod (Proxy @cM) (g . f) ma + where + g = ($ b) + {-# INLINE mergeMethod #-} +instance {-# OVERLAPPABLE #-} + ( TypeError ('Text "Effect methods must be monadic actions or functions resulting in monadic actions") + , Monad m ) + => MonadicMethod a f fM m + +genericMergeContext :: MonadicMethods a m => m (a m) -> a m +genericMergeContext = mergeMonadicMethods +{-# INLINE genericMergeContext #-}
src/Control/Effects/List.hs view
@@ -1,5 +1,5 @@ {-# LANGUAGE ScopedTypeVariables, TypeFamilies, FlexibleContexts, MultiParamTypeClasses #-} -{-# LANGUAGE DataKinds, GADTs #-} +{-# LANGUAGE DataKinds, GADTs, RankNTypes, NoMonomorphismRestriction #-} -- | Add non-determinism to your monad. Uses the 'ListT' transformer under the hood. module Control.Effects.List ( module Control.Effects.List @@ -11,22 +11,26 @@ import ListT hiding (take) import Control.Effects -import Data.Kind -newtype NonDeterministic = Choose Type -data instance Effect NonDeterministic method mr where - ChooseMsg :: { getChooseMsg :: [a] } -> Effect NonDeterministic ('Choose a) 'Msg - ChooseRes :: { getChooseRes :: a } -> Effect NonDeterministic ('Choose a) 'Res +data NonDeterminism +instance Effect NonDeterminism where + data EffMethods NonDeterminism m = NonDeterminismMethods + { _choose :: forall a. [a] -> m a } + liftThrough (NonDeterminismMethods c) = NonDeterminismMethods (lift . c) + mergeContext m = NonDeterminismMethods (\a -> do + lm <- m + _choose lm a) -instance Monad m => MonadEffect NonDeterministic (ListT m) where - effect (ChooseMsg list) = ChooseRes <$> fromFoldable list +-- | Get a value from the list. The choice of which value to take is non-deterministic +-- in a sense that the rest of the computation will be ran once for each of them. +choose :: forall a m. MonadEffect NonDeterminism m => [a] -> m a +NonDeterminismMethods choose = effect --- | Runs the rest of the computation for every value in the list -choose :: MonadEffect NonDeterministic m => [a] -> m a -choose = fmap getChooseRes . effect . ChooseMsg +instance Monad m => MonadEffect NonDeterminism (ListT m) where + effect = NonDeterminismMethods fromFoldable -- | Signals that this branch of execution failed to produce a result. -deadEnd :: MonadEffect NonDeterministic m => m a +deadEnd :: MonadEffect NonDeterminism m => m a deadEnd = choose [] -- | Execute all the effects and collect the result in a list. @@ -56,3 +60,7 @@ -- | Executes only the effects needed to produce a single result. evaluateOneResult :: Monad m => ListT m a -> m (Maybe a) evaluateOneResult = head + +-- | Execute all the effects but discard their results. +evaluateAll :: Monad m => ListT m a -> m () +evaluateAll = void . evaluateToList
src/Control/Effects/Reader.hs view
@@ -1,5 +1,6 @@ {-# LANGUAGE ScopedTypeVariables, TypeFamilies, FlexibleContexts #-} {-# LANGUAGE DataKinds, GADTs #-} +{-# LANGUAGE DeriveGeneric #-} -- | The regular old 'MonadReader' effect with some differences. First, there's no functional -- dependency limiting your stack to a single environment type. This means less type inference so -- it might not be enough to just write 'readEnv'. Write 'readEnv @MyEnvType' instead using @@ -7,22 +8,27 @@ -- -- Second, the function has a less generic name and is called 'readEnv'. -- --- Third, since it's a part of this effect framework, you get a 'handleReadEnv' function with +-- Third, since it's a part of this effect framework, you get a 'implementReadEnv' function with -- which you can provide a different environment implementation _at runtime_. module Control.Effects.Reader (module Control.Effects.Reader, module Control.Effects) where import Control.Effects +import GHC.Generics -data ReadEnv e = ReadEnv -data instance Effect (ReadEnv e) method mr where - ReadEnvMsg :: Effect (ReadEnv e) 'ReadEnv 'Msg - ReadEnvRes :: { getReadEnvRes :: e } -> Effect (ReadEnv e) 'ReadEnv 'Res +data ReadEnv e +instance Effect (ReadEnv e) where + data EffMethods (ReadEnv e) m = ReadEnvMethods + { _readEnv :: m e } + deriving (Generic) +-- | Read a value of type 'e'. Use with the TypeApplications extension to +-- help with type inference +-- @readEnv \@Int@ readEnv :: forall e m. MonadEffect (ReadEnv e) m => m e -readEnv = getReadEnvRes <$> effect ReadEnvMsg - -handleReadEnv :: Functor m => m e -> EffectHandler (ReadEnv e) m a -> m a -handleReadEnv m = handleEffect (\ReadEnvMsg -> ReadEnvRes <$> m) +readEnv = _readEnv effect -handleSubreader :: MonadEffect (ReadEnv e) m => (e -> e') -> EffectHandler (ReadEnv e') m a -> m a -handleSubreader f = handleReadEnv (f <$> readEnv) +-- | Use the given action in the underlying monad to provide environment +-- values. You can think of @implementReadEnv x m@ as replacing all 'readEnv' calls +-- in 'm' with 'x'. +implementReadEnv :: Functor m => m e -> RuntimeImplemented (ReadEnv e) m a -> m a +implementReadEnv m = implement (ReadEnvMethods m)
src/Control/Effects/Signal.hs view
@@ -1,7 +1,8 @@ {-# LANGUAGE TypeFamilies, ScopedTypeVariables, FlexibleContexts, Rank2Types, ConstraintKinds #-} {-# LANGUAGE MultiParamTypeClasses, NoMonomorphismRestriction #-} {-# LANGUAGE FlexibleInstances, UndecidableInstances, DataKinds, TypeOperators #-} -{-# LANGUAGE GADTs, TypeApplications #-} +{-# LANGUAGE GADTs, DeriveGeneric #-} +{-# LANGUAGE InstanceSigs #-} {-# OPTIONS_GHC -Wno-redundant-constraints #-} -- | This effect allows you to "throw" a signal. For the most part signals are the same as checked -- exceptions. The difference here is that the handler has the option to provide the value that @@ -9,22 +10,28 @@ -- recoverable exceptions at the throw site, instead of just at the handling site. module Control.Effects.Signal ( ResumeOrBreak(..), Signal, throwSignal, handleSignal - , Throws, handleException, handleToEither, module Control.Effects + , Throw, handleException, handleToEither, module Control.Effects , module Control.Monad.Trans.Except, MaybeT(..), discardAllExceptions, showAllExceptions - , Handles(..), handleToEitherRecursive, SomeSignal, signal ) where + , HandleException, handleWithoutDiscarding, handleToEitherRecursive, SomeSignal, signal + , EffMethods(..) ) where -import Import +import Import hiding (liftThrough) import Control.Monad.Trans.Except import qualified GHC.TypeLits as TL import GHC.TypeLits (TypeError, ErrorMessage(..)) import Control.Effects import Control.Monad.Runnable +import GHC.Generics -data Signal a b = Signal -data instance Effect (Signal a b) method mr where - SignalMsg :: a -> Effect (Signal a b) 'Signal 'Msg - SignalRes :: { getSignalRes :: b } -> Effect (Signal a b) 'Signal 'Res +data Signal a b +instance Effect (Signal a b) where + data EffMethods (Signal a b) m = SignalMethods + { _signal :: a -> m b } + deriving (Generic) +signal :: forall a b m. MonadEffect (Signal a b) m => a -> m b +SignalMethods signal = effect + newtype SomeSignal = SomeSignal { getSomeSignal :: Text } deriving (Eq, Ord, Read, Show) type family UnhandledError a b :: ErrorMessage where @@ -37,34 +44,30 @@ ':$$: 'TL.Text "You need to handle all the signals before running the computation" instance {-# OVERLAPPABLE #-} Monad m => MonadEffect (Signal e b) (ExceptT e m) where - effect (SignalMsg a) = throwE a + effect = SignalMethods throwE instance (Show e, Monad m) => MonadEffect (Signal e b) (ExceptT SomeSignal m) where - effect (SignalMsg a) = throwE . SomeSignal . pack . show $ a + effect = SignalMethods (throwE . SomeSignal . pack . show) instance Monad m => MonadEffect (Signal a b) (MaybeT m) where - effect _ = mzero -instance TypeError (UnhandledError a b) - => MonadEffect (Signal a b) IO where + effect = SignalMethods (const mzero) +instance TypeError (UnhandledError a b) => MonadEffect (Signal a b) IO where effect = undefined -instance {-# OVERLAPPING #-} (Monad m, b ~ c) => - MonadEffect (Signal a c) (EffectHandler (Signal a b) m) where - effect msg = EffectHandler (ReaderT (($ msg) . getEffectWithKind)) - -signal :: MonadEffect (Signal a b) m => a -> m b -signal a = getSignalRes <$> effect (SignalMsg a) +instance {-# INCOHERENT #-} (Monad m, b ~ c) => + MonadEffect (Signal a c) (RuntimeImplemented (Signal a b) m) where + effect = mergeContext $ RuntimeImplemented (liftThrough <$> ask) -type Throws e m = MonadEffect (Signal e Void) m +type Throw e = Signal e Void -- | The handle function will return a value of this type. -data ResumeOrBreak b c = Resume b -- ^ Give a value to the caller of 'signal' and keep going. - | Break c -- ^ Continue the execution after the handler. The handler will - -- return this value +data ResumeOrBreak b c = + Resume b -- ^ Give a value to the caller of 'signal' and keep going. + | Break c -- ^ Continue the execution after the handler. The handler will return this value -- | Throw a signal with no possible recovery. The handler is forced to only return the 'Break' -- constructor because it cannot construct a 'Void' value. -- -- If this function is used along with 'handleAsException', this module behaves like regular -- checked exceptions. -throwSignal :: Throws a m => a -> m b +throwSignal :: MonadEffect (Throw a) m => a -> m b throwSignal = fmap absurd . signal resumeOrBreak :: (b -> a) -> (c -> a) -> ResumeOrBreak b c -> a @@ -80,27 +83,26 @@ -- do and continue execution after the handler. handleSignal :: forall a b c m. Monad m => (a -> m (ResumeOrBreak b c)) - -> EffectHandler (Signal a b) (ExceptT c m) c + -> RuntimeImplemented (Signal a b) (ExceptT c m) c -> m c handleSignal f = fmap collapseEither - . runExceptT - . handleEffect h + . runExceptT + . implement (SignalMethods h) where - h :: forall method. Effect (Signal a b) method 'Msg -> ExceptT c m (Effect (Signal a b) method 'Res) - h (SignalMsg a) = do + h a = do rb <- lift (f a) - resumeOrBreak (return . SignalRes) throwE rb + resumeOrBreak return throwE rb -- | This handler can only behave like a regular exception handler. If used along with 'throwSignal' -- this module behaves like regular checked exceptions. -handleException :: Monad m => (a -> m c) -> ExceptT a m c -> m c +handleException :: forall a c m. Monad m => (a -> m c) -> ExceptT a m c -> m c handleException f = either f return <=< runExceptT -- | See documentation for 'handleException'. This handler gives you an 'Either'. -handleToEither :: ExceptT e m a -> m (Either e a) +handleToEither :: forall e a m. ExceptT e m a -> m (Either e a) handleToEither = runExceptT --- | Discard all the 'Throws' and 'Signal' constraints. If any exception was thrown +-- | Discard all the 'Throw' and 'Signal' effects. If any exception was thrown -- the result will be 'Nothing'. discardAllExceptions :: MaybeT m a -> m (Maybe a) discardAllExceptions = runMaybeT @@ -109,53 +111,61 @@ mapLeft f (Left a) = Left (f a) mapLeft _ (Right b) = Right b --- | Satisfies all the 'Throws' and 'Signal' constraints /if/ they all throw 'Show'able +-- | Satisfies all the 'Throw' and 'Signal' constraints /if/ they all throw 'Show'able -- exceptions. The first thrown exception will be shown and returned as a 'Left' result. showAllExceptions :: Functor m => ExceptT SomeSignal m a -> m (Either Text a) showAllExceptions = fmap (mapLeft getSomeSignal) . runExceptT --- | A class of monads that throw and catch exceptions of type @e@. An overlappable instance is --- given so you just need to make sure your transformers have a 'RunnableTrans' instance. -class Throws e m => Handles e m where - -- | Use this function to handle exceptions without discarding the 'Throws' constraint. - -- You'll want to use this if you're writing a recursive function. Using the regular handlers - -- in that case will result with infinite types. - -- - -- Since this function doesn't discard constraints, you still need to handle the whole thing. - -- - -- Here's a slightly contrived example. - -- - -- @ - -- data NotFound = NotFound - -- data Tree a = Leaf a | Node (Tree a) (Tree a) - -- data Step = GoLeft | GoRight - -- findIndex :: (Handles NotFound m, Eq a) => a -> Tree a -> m [Step] - -- findIndex x (Leaf a) | x == a = return [] - -- | otherwise = throwSignal NotFound - -- findIndex x (Node l r) = ((GoLeft :) <$> findIndex x l) - -- & handleRecursive (\NotFound -> (GoRight :) <$> findIndex x r) - -- @ - -- - -- Note: When you finally handle the exception effect, the order in which you handle it and - -- other effects determines whether 'handleRecursive' rolls back other effects if an exception - -- occured or it preserves all of them up to the point of the exception. - -- Handling exceptions last and handling them first will produce the former and latter - -- behaviour respectively. - handleRecursive :: (e -> m a) -> m a -> m a - -instance Monad m => Handles e (ExceptT e m) where - handleRecursive f = ExceptT . (either (runExceptT . f) (return . Right) <=< runExceptT) - {-# INLINE handleRecursive #-} - -instance {-# OVERLAPPABLE #-} (Monad m, Monad (t m), Handles e m, RunnableTrans t) - => Handles e (t m) where - handleRecursive f e = do +data HandleException e +instance Effect (HandleException e) where + data EffMethods (HandleException e) m = HandleExceptionMethods + { _handleWithoutDiscarding :: forall a. (e -> m a) -> m a -> m a } + type CanLift (HandleException e) t = RunnableTrans t + liftThrough :: + forall t m. (CanLift (HandleException e) t, Monad m, Monad (t m)) + => EffMethods (HandleException e) m -> EffMethods (HandleException e) (t m) + liftThrough (HandleExceptionMethods rec') = HandleExceptionMethods $ \f e -> do st <- currentTransState - res <- lift (handleRecursive (\ex -> runTransformer (f ex) st) (runTransformer e st)) + res <- lift (rec' (\ex -> runTransformer (f ex) st) (runTransformer e st)) restoreTransState res - {-# INLINE handleRecursive #-} + mergeContext m = HandleExceptionMethods $ \f ex -> do + g <- _handleWithoutDiscarding <$> m + g f ex +-- | Use this function to handle exceptions without discarding the 'Throw' effect. +-- You'll want to use this if you're writing a recursive function. Using the regular handlers +-- in that case will result with infinite types. +-- +-- Since this function doesn't discard constraints, you still need to handle the exception on +-- the whole computation. +-- +-- Here's a slightly contrived example. +-- +-- @ +-- data NotFound = NotFound +-- data Tree a = Leaf a | Node (Tree a) (Tree a) +-- data Step = GoLeft | GoRight +-- findIndex :: (Handles NotFound m, Eq a) => a -> Tree a -> m [Step] +-- findIndex x (Leaf a) | x == a = return [] +-- | otherwise = throwSignal NotFound +-- findIndex x (Node l r) = ((GoLeft :) <$> findIndex x l) +-- & handleWithoutDiscarding (\NotFound -> (GoRight :) <$> findIndex x r) +-- @ +-- +-- Note: When you finally handle the exception effect, the order in which you handle it and +-- other effects determines whether 'handleWithoutDiscarding' rolls back other effects if an exception +-- occured or it preserves all of them up to the point of the exception. +-- Handling exceptions last and handling them first will produce the former and latter +-- behaviour respectively. +handleWithoutDiscarding :: + forall e m a. MonadEffect (HandleException e) m => (e -> m a) -> m a -> m a +HandleExceptionMethods handleWithoutDiscarding = effect + +instance Monad m => MonadEffect (HandleException e) (ExceptT e m) where + effect = HandleExceptionMethods $ \f -> + ExceptT . (either (runExceptT . f) (return . Right) <=< runExceptT) + -- | 'handleToEither' that doesn't discard 'Throws' constraints. See documentation for --- 'handleRecursive'. -handleToEitherRecursive :: Handles e m => m a -> m (Either e a) -handleToEitherRecursive = handleRecursive (return . Left) . fmap Right +-- 'handleWithoutDiscarding'. +handleToEitherRecursive :: MonadEffect (HandleException e) m => m a -> m (Either e a) +handleToEitherRecursive = handleWithoutDiscarding (return . Left) . fmap Right
src/Control/Effects/State.hs view
@@ -1,6 +1,9 @@-{-# LANGUAGE TypeFamilies, ScopedTypeVariables, FlexibleContexts, Rank2Types, ConstraintKinds #-} +{-# LANGUAGE TypeFamilies, ScopedTypeVariables, FlexibleContexts, Rank2Types #-} {-# LANGUAGE MultiParamTypeClasses, GADTs #-} -{-# LANGUAGE DataKinds, TypeInType #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE NoMonomorphismRestriction #-} -- | The 'MonadState' you know and love with some differences. First, there's no functional -- dependency limiting your stack to a single state type. This means less type inference so -- it might not be enough to just write 'getState'. Write 'getState @MyStateType' instead using @@ -8,59 +11,63 @@ -- -- Second, the functions have less generic names and are called 'getState' and 'setState'. -- --- Third, since it's a part of this effect framework, you get a 'handleState' function with +-- Third, since it's a part of this effect framework, you get an 'implement' function with -- which you can provide a different state implementation _at runtime_. module Control.Effects.State (module Control.Effects.State, module Control.Effects) where import Import hiding (State) import Data.IORef +import GHC.Generics import Control.Effects -data State s = Get | Set -data instance Effect (State s) method mr where - GetStateMsg :: Effect (State s) 'Get 'Msg - GetStateRes :: { getGetStateRes :: s } -> Effect (State s) 'Get 'Res - SetStateMsg :: s -> Effect (State s) 'Set 'Msg - SetStateRes :: Effect (State s) 'Set 'Res +data State s -instance Monad m => MonadEffect (State s) (StateT s m) where - effect GetStateMsg = GetStateRes <$> get - effect (SetStateMsg s) = SetStateRes <$ put s - {-# INLINE effect #-} +instance Effect (State s) where + data EffMethods (State s) m = StateMethods + { _getState :: m s + , _setState :: s -> m () } + deriving (Generic) +-- | Get current value of the state with the type 's'. +-- You can use type applications to tell the type checker which type of state you want. +-- @getState \@Int@ getState :: forall s m. MonadEffect (State s) m => m s -getState = getGetStateRes <$> effect GetStateMsg -{-# INLINE getState #-} - +-- | Set a new value for the state of type 's' +-- You can use type applications to tell the type checker which type of state you're setting. +-- @setState \@Int 5@ setState :: forall s m. MonadEffect (State s) m => s -> m () -setState s = void $ effect (SetStateMsg s) -{-# INLINE setState #-} +StateMethods getState setState = effect +-- | Transform the state of type 's' using the given function. +-- You can use type applications to tell the type checker which type of state you're modifying. +-- @modifyState \@Int (+ 1)@ modifyState :: forall s m. MonadEffect (State s) m => (s -> s) -> m () modifyState f = do - s <- getState - let s' = f s in s' `seq` setState s' -{-# INLINE modifyState #-} + s <- getState @s + let s' = f s + s' `seq` setState s' --- | Handle the 'MonadEffect (State s)' constraint by providing custom handling functions. -handleState :: forall m s a. Monad m => m s -> (s -> m ()) - -> EffectHandler (State s) m a -> m a -handleState getter setter = - handleEffect handler - where handler :: forall method. Effect (State s) method 'Msg -> m (Effect (State s) method 'Res) - handler GetStateMsg = GetStateRes <$> getter - handler (SetStateMsg s) = SetStateRes <$ setter s -{-# INLINE handleState #-} +instance Monad m => MonadEffect (State s) (StateT s m) where + effect = StateMethods get put --- | Handle the state requirement using an 'IORef'. -handleStateIO :: MonadIO m => s -> EffectHandler (State s) m a -> m a -handleStateIO initial m = do +-- | Implement the state effect via the StateT transformer. If you have a function with a type like +-- @f :: MonadEffect (State Int) m => m ()@ you can use 'implementStateViaStateT' to satisfy the +-- 'MonadEffect' constraint. +-- +-- @implementStateViaStateT \@Int 0 f :: Monad m => m ()@ +implementStateViaStateT :: forall s m a. Monad m => s -> StateT s m a -> m a +implementStateViaStateT = flip evalStateT + +-- | Handle the state requirement using an 'IORef'. If you have a function with a type like +-- @f :: MonadEffect (State Int) m => m ()@ you can use 'implementStateViaIORef' to replace the +-- 'MonadEffect' constraint with 'MonadIO'. This is convenient if you already have a 'MonadIO' +-- constraint and you don't want to use the 'StateT' transformer for some reason. +-- +-- @implementStateViaIORef \@Int 0 f :: MonadIO m => m ()@ +implementStateViaIORef :: forall s m a. MonadIO m => s -> RuntimeImplemented (State s) m a -> m a +implementStateViaIORef initial m = do ref <- liftIO (newIORef initial) - m & handleState (liftIO (readIORef ref)) (liftIO . writeIORef ref) -{-# INLINE handleStateIO #-} + m & implement (StateMethods (liftIO (readIORef ref)) (liftIO . writeIORef ref)) +{-# INLINE implementStateViaIORef #-} --- | Handle the state requirement using the standard 'StateT' transformer. -handleStateT :: Monad m => s -> StateT s m a -> m a -handleStateT = flip evalStateT -{-# INLINE handleStateT #-}
src/Control/Monad/Runnable.hs view
@@ -1,5 +1,5 @@ {-# LANGUAGE TypeFamilies, UndecidableInstances, ScopedTypeVariables, FlexibleInstances #-} -{-# LANGUAGE ExistentialQuantification #-} +{-# LANGUAGE ExistentialQuantification, DefaultSignatures #-} {-# OPTIONS_GHC -Wno-warnings-deprecations #-} module Control.Monad.Runnable where @@ -37,12 +37,18 @@ -- 'runTransformer' function. runMonad :: MonadicState m -> m a -> IO (MonadicResult m a) + default runMonad :: PureRunnable m => MonadicState m -> m a -> IO (MonadicResult m a) + runMonad s m = return (runPureMonad s m) + +class Runnable m => PureRunnable m where + runPureMonad :: MonadicState m -> m a -> MonadicResult m a + -- | A class of transformers that can run their effects in the underlying monad. -- -- The following laws need to hold. -- -- @ --- \t -> do st <- 'currentTransState' +-- \\t -> do st <- 'currentTransState' -- res <- 'lift' ('runTransformer' t st) -- 'restoreTransState' res -- == 'id' @@ -50,7 +56,7 @@ -- -- @ -- f :: (forall a. m a -> m a) --- \m s -> runTransformer (lift (f m)) s == \m s -> f (runTransformer (lift m) s) +-- \\m s -> runTransformer (lift (f m)) s == \\m s -> f (runTransformer (lift m) s) -- @ class MonadTrans t => RunnableTrans t where -- | The type of value that needs to be provided to run this transformer. @@ -59,7 +65,7 @@ type TransformerResult t (m :: * -> *) a :: * -- | Get the current state value. currentTransState :: Monad m => t m (TransformerState t m) - -- | If given a result, reconstruct the compitation. + -- | If given a result, reconstruct the computation. restoreTransState :: Monad m => TransformerResult t m a -> t m a -- | Given the required state value and a computation, run the effects of the transformer -- in the underlying monad. @@ -70,8 +76,10 @@ type MonadicResult Identity a = a currentMonadicState = return () restoreMonadicState = return - runMonad _ (Identity a) = return a +instance PureRunnable Identity where + runPureMonad _ (Identity a) = a + instance Runnable IO where type MonadicState IO = () type MonadicResult IO a = a @@ -85,6 +93,9 @@ currentMonadicState = (,) <$> currentTransState <*> lift currentMonadicState restoreMonadicState s = lift (restoreMonadicState s) >>= restoreTransState runMonad (s, s') t = runMonad s' (runTransformer t s) + +instance (PureRunnable m, RunnableTrans t, Monad (t m)) => PureRunnable (t m) where + runPureMonad (s, s') t = runPureMonad s' (runTransformer t s) instance RunnableTrans (SS.StateT s) where type TransformerState (SS.StateT s) m = s
+ src/Tutorial/T1_Introduction.hs view
@@ -0,0 +1,234 @@+{-# OPTIONS_GHC -Wno-unused-imports #-} +{-| This part of the tutorial will explain the basics behind @simple-effects@ and how to use the + effects provided by the library. To learn how to implement your own effects, check out the other + parts. + + You'll need to enable some extensions to follow along: @TypeApplications@, @FlexibleContexts@, + @OverloadedStrings@, @DataKinds@. +-} +module Tutorial.T1_Introduction ( + -- * State + -- $state + + -- * Non-determinism + -- $nondeterminism + + -- * Order + -- $order + ) where + +import Data.Text as T +import Data.Text.IO as T +import Control.Monad.IO.Class +import Control.Effects.State +import Control.Effects.List +{- $state + Let's say we're writing a function that asks the user to name a fruit and adds their answer to + a list of already known fruits. Here's what we want to do (in pseudocode) + +@ +addFruit = do + fruit <- ask "Name a type of fruit please" + knownFruits <- getCurrentlyKnownFruits + setCurrentlyKnownFruits (fruit : knownFruits) +@ + + Our function needs to get input from the user, so we'll need a way to do IO. It also modifies + a piece of state: a list of fruits. We state those requirements in the signature. + +@ +addFruit :: ('MonadIO' m, 'MonadEffect' ('State' ['Text']) m) => m () +@ + + The 'MonadIO' constraint comes from "Control.Monad.IO.Class" of the @transformers@ package and + it lets us do IO without forcing our monad to be 'IO'. + + The other constraint, 'MonadEffect', is how you specify effects using this library. It says that + to run @addFruit@ you need to provide an implementation for a state holding a list of 'Text's. + + Here's how we might define the @addFruit@ function. + +@ +addFruit :: ('MonadIO' m, 'MonadEffect' ('State' ['Text']) m) => m () +addFruit = do + 'liftIO' ('T.putStrLn' "Name a type of fruit please") + fruit <- 'liftIO' 'T.getLine' + knownFruits <- 'getState' + 'setState' (fruit : knownFruits) +@ + + [Note] + It's possible to have more than one state available to use. Since 'getState' and 'setState' + need to work with all of them you'll sometimes need to specify the type of state you want to get/set. + In the above case we didn't need to since the compiler can infer that our state is @['Text']@. + It infers @fruit :: 'Text'@ from the signature of 'T.getLine' and it can infer the list part + since we're using the list cons operator. + + To help the type checker in cases where it can't infer the types, it's convenient to use the + @TypeApplications@ extension. With it, we can write @fruit <- 'getState' \@['Text']@ and + @'setState' \@['Text'] (fruit : knownFruits)@ to be explicit about which state type we mean. + + + So lets use our function to ask for three types of fruit. After than we want to print the list. + +@ +-- this doesn't work yet +main :: 'IO' () +main = do + addFruit + addFruit + addFruit + fruits <- 'getState' \@['Text'] + 'liftIO' ('print' fruits) +@ + + This almost works but we still need to provide an implementation of the state. The simplest way + to do that is the 'implementStateViaStateT' function. It takes an initial value, and a computation + that has a state requirement, and it satisfies that requirement. In this case, the initial + value will be an empty list, and the computation will be our whole do block. + +@ +main :: 'IO' () +main = 'implementStateViaStateT' \@['Text'] [] $ do + addFruit + addFruit + addFruit + fruits <- 'getState' \@['Text'] + 'liftIO' ('print' fruits) +@ + + This should work. The reason we don't need to to anything about the 'MonadIO' constraint is + because the fact that the final result is in the 'IO' monad automatically satisfies it. + + Another thing that we can do with the 'State' effect (and all the other ones provided by the + @simple-effects@ library) is provide a custom implementation that can depend on runtime values. + + Lets imagine that we have a database and two functions: + +@ +getFruits :: 'MonadIO' m => Connection -> m ['Text'] +setFruits :: 'MonadIO' m => Connection -> ['Text'] -> m () +@ + + These should get a list of fruits from the database, and store a new list back into it. We can + use the exact same code as above, just changing the part that implements the state: + +@ +main :: 'IO' () +main = do + conn <- connectToDb "my-connection-string" + 'implement' ('StateMethods' (getFruits conn) (setFruits conn)) $ do + addFruit + addFruit + addFruit + fruits <- 'getState' \@['Text'] + 'liftIO' ('print' fruits) +@ + + And now suddenly our fruit list persists between sessions. We can do the same but instead talk + to some remote API, or read/write from a file, or use a shared variable and run multiple + computations at the same time... + +-} + +{- $nondeterminism + Now lets add an additional effect into the mix. For example, we an use the 'NonDeterminism' effect. + +@ +main :: 'IO' () +main = + 'evaluateAll' $ + 'implementStateViaStateT' \@['Text'] [] $ do + addFruit + addFruit + addFruit + fruits <- 'getState' \@['Text'] + fruit <- 'choose' fruits + 'liftIO' ('print' fruit) +@ + + The 'choose' function non-deterministically picks one fruit from the list and prints it. When + we use it, our @do@ block gets a new constraint. It's type is now + +@ +('MonadIO' m, 'MonadEffect' ('State' ['Text']) m, 'MonadEffect' 'NonDeterminism' m) => m () +@ + + Therefore we need to provide an implementation of the 'NonDeterminism' effect before we run the + whole thing. This is what the 'evaluateAll' function does. It runs the computation for each + non-deterministic possibility, meaning all the fruit will get printed. + + [Note] Instead of repeating 'MonadEffect' for each effect, you can use the 'MonadEffects' + type family and give it a list of effects instead. Like this + @('MonadIO' m, 'MonadEffects' \'['State' ['Text'], 'NonDeterminism'] m) => m ()@ + +-} + +{- $order + One thing to note is that the order in which you implement effects sometimes matters. + For example, handling state first and then non-determinism after will result in the state + being forked on each non-deterministic branch. Doing it in the reverse order will make the + state shared between branches meaning that changes in one branch will affect the state when the + next branch is taken. Here's an example + +@ +main :: IO () +main = do + 'evaluateAll' $ + 'implementStateViaStateT' \@Int 0 $ do + 'setState' \@Int 1 + 'choose' ('Prelude.replicate' 3 ()) + 'setState' . 'succ' =<< 'getState' \@Int + 'liftIO' . 'print' =<< 'getState' \@Int + 'Prelude.putStrLn' "" + 'implementStateViaStateT' \@Int 0 $ + 'evaluateAll' $ do + 'setState' \@Int 1 + 'choose' ('Prelude.replicate' 3 ()) + 'setState' . 'succ' =<< 'getState' \@Int + 'liftIO' . 'print' =<< 'getState' \@Int +@ + The first run prints @2 2 2@ because the state is handled first, while the second run prints + @2 3 4@. A useful way to get an intuitive understanding of which order does what is to consider + that in the second case, after we handle non-determinism we can still get the state value because + we have yet to handle that effect. But if state was forked on each choice there could be many + possible state values after the whole computation finishes. Which one would 'getState' return? + The only way it makes sense is if the state is shared since it keeps things unambiguous. + + On the other hand maybe we do want to see all the possible end states. The following example + demonstrates how the two orderings let us do those two things. + +@ +main4 :: IO () +main4 = do + lst <- 'evaluateToList' $ + 'implementStateViaStateT' \@Int 0 $ do + 'setState' \@Int 1 + 'choose' ('Prelude.replicate' 3 ()) + 'setState' . 'succ' =<< 'getState' \@Int + 'getState' \@Int + 'print' lst + 'implementStateViaStateT' \@Int 0 $ do + 'evaluateAll' $ do + 'setState' \@Int 1 + 'choose' ('Prelude.replicate' 3 ()) + 'setState' . 'succ' =<< 'getState' \@Int + 'liftIO' . 'print' =<< 'getState' \@Int +@ + + The first run gets the state value at the end. This will be the end state for each fork. This + value becomes the result of the whole 'implementStateViaStateT' block but since we still need + to handle the non-determinism, this whole block is also ran once for each possible branch. + Finally, we collect all the results in a list using the 'evaluateToList' function. This handles + the non-determinism. + + In the second run we get the state /after/ handling non-determinism. This gives us the final + value of the state shared between all the branches. + + This concludes the first part of the tutorial. To see how other effects are used, check out the + examples and the documentation of their respective modules. + + If you want a more in-depth look into the inner workings of this library, continue to the second + part of the tutorial: "Tutorial.T2_Details". We'll look at implementing custom effects in part 3, + "Tutorial.T3_CustomEffects". +-}
+ src/Tutorial/T2_Details.hs view
@@ -0,0 +1,288 @@+{-# OPTIONS_GHC -Wno-unused-imports #-} +{-| In this part, we'll take a more detailed look at this library. You don't need to know these + things to use the effects. For the most part, you can also implement your own without reading + this part. To learn about that check out the next part: "Tutorial.T3_CustomEffects". + + That being said, the details will help you understand potential compiler errors you might get. + Also, implementing more complex effects does require a bit of an understanding of the internals. +-} + +module Tutorial.T2_Details ( + -- * Transformers + -- $transformers + + -- * The 'Effect' class + -- $effect + + -- * The 'MonadEffect' class + -- $monadeffect + + -- * Runtime implementation + -- $runtime + ) where + +import Control.Effects +import Control.Effects.State +import Control.Monad.Trans.State as S hiding (State) +import Control.Monad.Trans.Reader as R +import Control.Monad.Trans as T (lift, MonadTrans) +import GHC.Generics +{- $transformers + Monad transformers are types with the kind @(\* -> \*) -> \* -> \*@ that take a monad as a + parameter and add some extra functionality on top of it, resulting in a new monad. This isn't a + tutorial on them so we'll just briefly go through the basics. + + Since a transformer takes a monad and produces a new monad, they can stack on top of each other + forming \'transformer stacks\'. The functions that utilize a transformer @t@\'s functionality only + work directly if @t@ is the topmost transformer on the stack. Since there can obviously only be + one transformer on the top, and we want to use more than one effect, we need to 'T.lift' their + functions through the stack. For example: +@ +'S.get' :: 'Monad' m => 'StateT' s m s +@ + so if we want to use this function in a stack that has a 'ReaderT' transformer on top we need to + use 'T.lift': + +@ +'T.lift' 'S.get' :: 'Monad' m => 'ReaderT' r ('StateT' s m) s +@ + + If you imagine we had 5 additional layers above the 'ReaderT', we'd need to call 'T.lift' 5 more + times. Instead we use type classes with polymorphic functions that work over any transformer + stack. Their instances are arranged in a way that automatically calls 'T.lift' as many times as + needed. For example, the 'getState' function in @simple-effects@ has the type + @'MonadEffect' ('State' s) m => m s@. Let's say our stack is + @'ReaderT' r1 ('ReaderT' r2 ('StateT' s 'IO'))@. The reason why we can use the 'getState' + function is because there are two instances for @'MonadEffect' ('State' s)@. + +@ +instance 'MonadEffect' ('State' s) ('StateT' s m) +@ + + and + +@ +instance 'MonadEffect' ('State' s) m => 'MonadEffect' ('State' s) ('ReaderT' r m) +@ + + Instance resolution first matches the second instance and sees that it requires a + @'MonadEffect' ('State' s)@ from the underlying monad @m@. Then it again encounters a + 'ReaderT' and finally it arrives at the 'StateT' layer that has no other requirements. + The actual implementation for the 'ReaderT' instance does the 'T.lift'ing while the 'StateT' + implementation actually uses the structure of the 'StateT' transformer to implement the + required function. + + [Note] + This is a bit of a simplification. There isn't really a + @'MonadEffect' ('State' s) ('ReaderT' r m)@ instance, but instead a more general + @'MonadEffect' e (t m)@ one that works for any transformer and any effect. It's an + @OVERLAPPABLE@ instance so more specific ones can be chosen if they exist (as is the case + with, for example, the 'StateT' instance for the 'State' effect) + + Now let's see how a function like 'implementStateViaStateT' works. It's type is + @'implementStateViaStateT' \@Int :: 'Monad' m => Int -> 'StateT' Int m a -> m a@. Say we also have a + computation with the type @'MonadEffect' ('State' Int) n => n ()@ that we want to run. If we + give that computation as the second parameter of the 'implementStateViaStateT' function then @n@ + becomes @'StateT' Int m a@, but since there was also a constraint on the @n@ type, instance + resolution must check if that type satisfies it. This then finds the + @instance 'MonadEffect' ('State' s) ('StateT' s m)@ instance and everything works. Then finally + the actual definition of 'implementStateViaStateT' kicks in and it runs the 'StateT' transformer, + giving it an initial state value and resulting in a monadic action that's free of the state + constraint. + + If we had additional effect constraints on our initial computation, then the instance resolution + would find the matching instances for the 'StateT' transformer that would push the constraint + onto the underlying monad. This means that our final result wouldn't just have a 'Monad' + constraint, but also those other ones that remain to be handled. + + What @simple-effects@ does is provide a structured way to define new effects so that they can + automatically be lifted through monad stacks. Also, for the more complex effects where just + 'lift' isn't enough, it enables the writer of the effect to specify how exactly to lift their + effect. This is done through the 'Effect' class. +-} +{- $effect + The core of every effect is it's 'Effect' instance. Here's how the class is defined: + +@ +class 'Effect' e where + data 'EffMethods' e (m :: * -> *) :: * + type 'CanLift' e (t :: (* -> *) -> * -> *) :: 'Constraint' + type 'CanLift' e t = 'MonadTrans' t + 'liftThrough' :: + ('CanLift' e t, 'Monad' m, 'Monad' (t m)) + => 'EffMethods' e m -> 'EffMethods' e (t m) + 'mergeContext' :: 'Monad' m => m ('EffMethods' e m) -> 'EffMethods' e m +@ + + The 'EffMethods' associated data type is where you specify the functionality that your effect + provides. This will be a record of monadic functions. For example, there's how it's instantiated + for the state effect: + +@ +data 'EffMethods' ('State' s) m = 'StateMethods' + { '_getState' :: m s + , '_setState' :: s -> m () } +@ + + Then there's the 'CanLift' type. It specifies the constraint that a transformer needs to satisfy + to be able to lift the effect through it. Usually, 'MonadTrans' is all you need, so that's the + default instantiation. Some more complicated effects have tighter demands. + + The 'liftThrough' function gets an implementation of the effect @e@ in the monad @m@ and is + required to provide an implementation in the monad @t m@. Here's how it might look for the + 'State' effect. + +@ +'liftThrough' ('StateMethods' g s) = 'StateMethods' ('lift' g) ('lift' . s) +@ + + As you can see, 'MonadTrans' is enough in this case since the 'lift' function is all we need. + + Finally, the 'mergeContext' method. Given a record of methods in a monadic context, give me a + new record of methods that somehow merges that context into it. The way it's implemented is + simpler than it seems. For example, let's see how we'd implement a function with the following + signature: @f :: m (a -> m b) -> a -> m b@ + + Since our result is monadic we're free to just bind that inner function and give it the extra + parameter like this: + +@ +f mamb a = do + amb <- mamb + amb a +@ + + If we had more than one function inside of the context, we'd just need to select the correct one + after binding it. Here's how 'mergeContext' is implemented for the 'State' effect. + +@ +'mergeContext' m = 'StateMethods' + { '_getState' = do + sm <- m + '_getState' sm + , '_setState' s = do + sm <- m + '_setState' sm s } +@ + + If these implementations seem pretty mechanical it's because they are. So mechanical, in fact, + that in most of the cases you don't even need to write them. Just derive the 'Generic' class + for your effect and you get those definitions for free. Here's the actual instance + @'Effect' ('State' s)@: + +@ +instance 'Effect' ('State' s) where + data 'EffMethods' ('State' s) m = 'StateMethods' + { '_getState' :: m s + , '_setState' :: s -> m () } + deriving ('Generic') +@ + + There are a couple of conditions though. The automatic method deriving only works for what + is called, in the terminology of this library, a simple effect. The methods of a simple effect + are functions that take arguments that don't depend on the monad @m@ and also have a monadic + result in that monad. For example, @'setState' :: s -> m ()@ is a method of a simple effect + while @g :: m a -> m a@ isn't one because the first parameter depends on @m@. + + One extra condition is that there can't be any universally quantified variables in the methods, + so for example if your effect has a method @h :: forall a. a -> m ()@, you can't derive + 'liftThrough' and 'mergeContext' for it automatically. This isn't because it's impossible + (or even difficult), but because you can't derive 'Generic' for those types. In those cases + you need to write the implementations yourself. + + Finally, we have the 'MonadEffect' class. +-} + +{- $monadeffect + It's defined like this + +@ +class ('Effect' e, 'Monad' m) => 'MonadEffect' e m where + 'effect' :: 'EffMethods' e m +@ + + So it just says that monads that implement the effect @e@ need to provide a record of + the effect's methods that work in that monad. + + As discussed in the transformers section, there's an + overlappable instance given for the 'MonadEffect' class. Here it is: + +@ +instance \{\-\# OVERLAPPABLE \#\-\} + ('MonadEffect' e m, 'Monad' (t m), 'CanLift' e t) => 'MonadEffect' e (t m) where + 'effect' = 'liftThrough' 'effect' +@ + + It means that any transformer @t@ can implement the effect @e@ as long as the underlying monad + can implement it /and/ the transformer satisfies the conditions that the effect imposes on it + (in most cases, the 'MonadTrans' constraint). + + Transformers that finally implement the effect have specific 'MonadEffect' instances. For + example: + +@ +instance 'Monad' m => 'MonadEffect' ('State' s) ('StateT' s m) where + 'effect' = 'StateMethods' 'S.get' 'S.put' +@ + + [Note] + The 'S.get' and 'S.put' functions come from the "Control.Monad.Trans.State" module. +-} + +{- $runtime + Defining effects in a structured way like this has an additional benefit. It lets us use + implementations that we construct at runtime! This is achieved through the 'RuntimeImplemented' + type. 'RuntimeImplemented' is a special transformer defined like this: + +@ +newtype 'RuntimeImplemented' e m a = 'RuntimeImplemented' + { 'getRuntimeImplemented' :: 'ReaderT' ('EffMethods' e m) m a } +@ + + It's a wrapper around a 'ReaderT' that carries around an @'EffMethods' e m@ record. + @'RuntimeImplemented' e@ has a @'MonadEffect' e@ instance defined like this + +@ +instance ('Effect' e, 'Monad' m, 'CanLift' e ('RuntimeImplemented' e)) + => 'MonadEffect' e ('RuntimeImplemented' e m) where + 'effect' = 'mergeContext' $ 'RuntimeImplemented' ('liftThrough' <$> 'ask') +@ + + Essentially, 'ask' gives us the record, but it's inside of a monadic context so we need + 'mergeContext' to get it out. This lets us implement any effect /at runtime/ using the + 'implement' function: + +@ +'implement' :: forall e m a. 'EffMethods' e m -> 'RuntimeImplemented' e m a -> m a +'implement' em ('RuntimeImplemented' r) = 'runReaderT' r em +@ + + As with 'implementStateViaStateT', when we give our polymorphic monadic action as a second + parameter, instance resolution checks if @'RuntimeImplemented' e m a@ has instances for all the + effects we need. Most of them just propagate through to the inner @m@ monad, but the effect @e@ + that we're implementing finds the instance above and uses that reader's environment to get the + effect's methods. All that's left is to actually run the 'ReaderT' by giving it the record which + we're free to construct any way we want. + + [Note] + One more thing. You might notice that the definition of the state effect doesn't mention the + 'getState' and 'setState' functions. The methods in the record are called '_getState' and + '_setState', with an underscore. If you think about it you'll see that to actually use one + of them, we first need to get them out of the record. Since we can access the record of + methods for the current monad with the 'effect' function, getting the current state would + look like @'_getState' 'effect'@ so to reduce duplication it's convenient to define helpers + like @'getState' = '_getState' 'effect'@ and @'setState' = '_setState' effect@. Even simpler, + though arguably more magical, is the following top level definition + + @ + 'StateMethods' 'getState' 'setState' = 'effect' + @ + + When we write that at the top level, we bind the first field in the 'effect' record to the + 'getState' name and the second field to the 'setState' name. It's pretty weird because we're + not in any specific monad, yet we're still somehow unpacking the record. Still, 'getState', + for example, happily accepts the @'MonadEffect' ('State' s) m => m s@ type signature and + it just works. + + That's pretty much it. In the next part we'll take a look at implementing our own effects. +-}
+ src/Tutorial/T3_CustomEffects.hs view
@@ -0,0 +1,419 @@+{-# OPTIONS_GHC -Wno-unused-imports #-} +{-| Let's see how we can implement custom effects. We'll go through a couple of examples of + increasing complexity. This part does not require going through part 2. That being said, + understanding the details will help with understanding some of the restrictions and will help + with compiler errors you might get. +-} +module Tutorial.T3_CustomEffects ( + -- * Files + -- $files + + -- * Implementations + -- $implement + + -- * Print + -- $print + + -- * Fork + -- $fork + ) where +import Control.Effects +import Control.Effects.State +import Control.Monad.Trans.State as S hiding (State) +import Control.Monad.Trans.Reader as R +import Control.Monad.Trans as T (lift, MonadTrans) +import GHC.Generics +import Data.Text +import Data.ByteString as B +import Control.Monad.IO.Class +import Control.Concurrent +{- $files + To start off, we'll define an effect for working with the filesystem. To keep it simple, we'll + define two functions. One for reading the contents of a file, and one for writing them to a file. + + The first step is to make a name for our effect. Say, @Files@ for example. We declare an empty + datatype with that name: + +@ +data Files +@ + + Next, we need to provide an instance of the 'Effect' class for our effect. + + The signatures of our functions will be @'FilePath' -> m 'ByteString'@ and + @'FilePath' -> 'ByteString' -> m ()@ respectively. Both of them are methods of what we call, in the + jargon of this library, a simple effect. It means that they are functions that return a monadic + action, and that their arguments don't depend on that monad. As an example, @m Int -> m Int@ + isn't a method of a simple effect because the argument depends on @m@. + + Because of this fact, the instance is super simple: + +@ +instance 'Effect' Files where + data 'EffMethods' Files m = FilesMethods + { _readFile :: 'FilePath' -> m 'ByteString' + , _writeFile :: 'FilePath' -> 'ByteString' -> m () } + deriving ('Generic') +@ + + The 'Effect' class requires you to provide a record of methods for your effect. This is done + with an associated data type. You can read about them here + <https://wiki.haskell.org/GHC/Type_families#Associated_family_declarations> + The class also has two functions: 'liftThrough' and 'mergeContext'. Luckily, because our effect + is simple (and mostly they will be), it's enough to just derive 'Generic' for our type. The + functions are then defined for us. + + It's a convention to name the methods starting with an underscore so we can define the following + helper functions: + +@ +readFile :: 'MonadEffect' Files m => 'FilePath' -> m 'ByteString' +writeFile :: 'MonadEffect' Files m => 'FilePath' -> 'ByteString' -> m () +FilesMethods readFile writeFile = 'effect' +@ + + So how does this work, and what does it even do? Well, we defined how a record of methods looks + for our effect, but how is that record actually constructed? If we wanted to /use/ our methods + where would we get them from? Enter the 'effect' function. It's type signature is + @'MonadEffect' e m => 'EffMethods' e m@. It means that for every monad @m@ which supports the + effect @e@, the 'effect' function gives us a record of the effect's methods. Let's say we want + to read the contents of a file. First we'd use the 'effect' function to get the methods of our + @Files@ effect, then we'd get the @_readFile@ function out of it, and finally we'd use that + function. + +@ +myFunction = do + let methods = 'effect' + let rfFunction = _readFile methods + rfFunction "somefile.txt" + -- or equivalently + _readFile 'effect' "somefile.txt" +@ + + Now since writing @_readFile 'effect'@ gets tedious, we can define new top level helper + functions: + +@ +readFile = _readFile 'effect' +writeFile = _writeFile 'effect' +@ + + The @_readFile@ and @_writeFile@ functions are nothing more than record selectors that get the + appropriate method from our @FilesMethods@ record. With this in mind we can skip them and just + directly pattern match on the @FilesMethods@ constructor like + +@ +FilesMethods readFile writeFile = 'effect' +@ + + Check out the previous part if you want to learn more. +-} +{- $implement + We've only defined the 'syntax' of our effect. How do we actually run those functions? This is + done through the 'implement' function. It lets us construct the implementations at runtime. + + Suppose we have a function like + +@ +myFunc :: 'MonadEffect' Files m => m () +myFunc = do + file <- readFile "file.dat" + newFile <- doSomething file + writeFile "file.dat" newFile +@ + + To use it in our program we need to handle the 'MonadEffect' constraint. This is how we might do + it: + +@ +main :: IO () +main = do + 'implement' (FilesMethods 'B.readFile' 'B.writeFile') myFunc +@ + + Here we implemented our effect using the 'B.readFile' and 'B.writeFile' functions from the + "Data.ByteString" module. Of course we're free to implement them however we want. In a testing + environment we might instead just read/write from a @'Map' 'FilePath' 'ByteString'@ and simulate + a filesystem. + + To make it easier to use our effect, it's a good idea to provide one or more default handlers. + For example, we might define two handlers: + +@ +implementFilesViaIO :: 'MonadIO' m => 'RuntimeImplemented' Files m a -> m a +implementFilesViaMap :: 'Monad' m => 'RuntimeImplemented' Files ('StateT' ('Map' 'FilePath' 'ByteString') m) a -> m a +@ + + This way the users of our effect don't have to implement the handlers themselves, but are still free + to implement more specialized ones. +-} +{- $print + For our next effect we'll do logging. Just a simple printing function that takes anything + with a 'Show' instance and logs it somewhere. + + The signature we want is @print :: ('MonadEffect' Print m, 'Show' a) => a -> m ()@. Now here's + the main issue. The @a@ variable isn't mentioned anywhere in the effect. After all, we don't + want a separate effect for each possible type. We want the @Print@ effect to provide printing + /for all/ types with a 'Show' instance. To this end we'll use the @RankNTypes@ extension and define + our 'Effect' instance like this: + +@ +data Print + +instance 'Effect' Print where + data 'EffMethods' Print m = PrintMethods + { _print :: forall a. Show a => a -> m () } +@ + + Notice we didn't derive 'Generic'. This is because we can't. Despite our effect being simple + (the function's parameter doesn't depend on @m@ and it returns a monadic action), the @forall@ + in there makes it impossible to derive a 'Generic' instance. Unfortunately, this means that we + have to implement the two functions of the 'Effect' class ourselves. Fortunately, it's a very + mechanical procedure (that's why they can usually be automatically derived!). + + The two functions are + +@ +'liftThrough' :: ('MonadTrans' t, 'Monad' m, 'Monad' (t m)) => 'EffMethods' e m -> 'EffMethods' e (t m) +@ + + and + +@ +'mergeContext' :: 'Monad' m => m ('EffMethods' e m) -> 'EffMethods' e m +@ + + [Note] + The 'MonadTrans' part is a slight simplification, but it's an honest one for our current + example. You can read more about the actual definitions and the purpose of these two + functions in the previous part of the tutorial. + + The first function, 'liftThrough', takes a record of methods of the effect @e@ for the monad + @m@, and is expected to return a new record, but this time for the monad @t m@. Two puzzle + pieces make this very easy to do. The first is the fact that the only place where @m@ is mentioned + in our effect is in the result of the @_print@ function. The second piece of the puzzle is the + @'lift' :: ('MonadTrans' t, 'Monad' m) => m a -> (t m) a@ function of the 'MonadTrans' class. So to + construct the new @_print@ method, we just call the old one and 'lift' the result: + +@ +'liftThrough' (PrintMethods pr) = PrintMethods (\\a -> 'lift' (pr a)) +@ + + This will work for as many methods with as many parameters as you want. The implementation will + always look something like + +@ +'liftThough' (MyMethods m1 m2 m3 m4) = MyMethods + (\\a -> 'lift' (m1 a)) + (\\a b c -> 'lift' (m2 a b c)) + (\\a b -> 'lift' (m3 a b)) + ('lift' m4) +@ + + Up next: 'mergeContext'. It says that given the record of methods /inside a monadic context/, + give me just the record, somehow pushing that context inside of it. The implementation is again + very mechanical. + +@ +'mergeContext' pm = PrintMethods + (\\a -> do + PrintMethods p <- pm + p a) +@ + + Essentially, we just pass the parameters through to the old record, but first we must actually + get the old record out of the monadic context. We can do that because each method's result is + a monadic action. Here's how it would look like for a bigger effect: + +@ +'mergeContext' mm = MyMethods + (\\a -> do + MyMethods m _ _ _ <- mm + m a) + (\\a b c -> do + MyMethods _ m _ _ <- mm + m a b c) + (\\a b -> do + MyMethods _ _ m _ <- mm + m a b) + (do + MyMethods _ _ _ m <- mm + m) +@ + + [Note] + Instead of pattern matching, we can use the name of our method as a field selector: + + @ + 'mergeContext' pm = PrintMethods + (\\a -> do + m <- pm + _print m a) + @ + + As you can see, there isn't much to these implementations. Just boilerplate. Also note that + while our @Print@ effect may seem simple, it's actually more complicated than it needs to be. + Instead we could have defined the whole thing like this: + +@ +data Print +instance 'Effect' Print where + data 'EffMethods' Print m = PrintMethods + { _printString :: 'String' -> m () } + deriving ('Generic') + +print :: ('MonadEffect' Print m, 'Show' a) => a -> m () +print = _printString 'effect' . 'show' +@ + + That way we still get a nice polymorhpic function, but the effect itself is monomorphic and + lets us get away with just deriving 'Generic'. + + Next, we'll look at a non-simple effect. One for which the 'liftThrough' method can't be + derived because there isn't just a single valid implementation. +-} +{- $fork + Here's the challenge. There's a 'forkIO' function in base with the following signature + +@ +'forkIO' :: IO () -> IO 'ThreadId' +@ + + We want to generalize this function to work with monads other than 'IO'. Essentially, we want + +@ +fork :: 'MonadEffect' Fork m => m () -> m ('Maybe' 'ThreadId') +@ + + [Note] + The 'Maybe' part is so we don't get tied down to 'IO'. Don't worry about it for now. + + Notice that this isn't a simple effect as the parameter is a monadic action. Anyways, let's try + defining our effect and see where we get stuck: + +@ +data Fork +instance 'Effect' Fork where + data 'EffMethods' Fork m = ForkMethods + { _fork :: m () -> m ('Maybe' 'ThreadId') } + 'mergeContext' mm = ForkMethods + (\\a -> do + ForkMethods m <- mm + m a) + 'liftThrough' (ForkMethods f) = ForkMethods + (\\a -> 'lift' (f a)) +@ + + Simple right? Well, unfortunately it doesn't typecheck. The problem is in the 'liftThrough' + function. Here are the relevant types: + +@ +f :: m () -> m 'ThreadId' +a :: t m () +@ + + The result we need is of type @t m 'ThreadId'@. If we could somehow get a @m 'ThreadId'@ we'd + be fine since just 'lift'ing that does the trick. The problem is, the only way to get a + @m 'ThreadId'@ is by calling @f@ with a @m ()@, and we don't have that. What we do have is + @a :: t m ()@ so it seems that we need a function that's opposite of 'lift'. Something like + @unlift :: t m a -> m a@. + + + Turns out, that's not so simple to do. Imagine you have a function like with a type @a -> m b@. + In this case the @a ->@ part is @t@. If we specialize @unlift@ to that we get + @unlift :: (a -> m b) -> m b@. There's no way to implement that function. To get @m b@ we need + to have an @a@, but none are given to us. + + In any case, even if @unlift@ was possible to implement, it's not like we could use it. The only + thing we know about @t@ is that it has a 'MonadTrans' instance (that's where we get the 'lift' + function from)... Well, not exactly. Remember that note about 'MonadTrans' being a + simplification? The 'Effect' class actually has an additional associated type. It lets us + require a custom constraint for our transformer so we can actually require @t@ to be an instance + of anything we like. + + [Note] + This extra power doesn't come for free, though. Stricter requirements mean that your effect can't + be used in certain situations. What this means exactly is a bit out of the scope of this tutorial, + but here's a quick rundown. + + Handling effects requires monad transformers. Each effect handled will result in at least one + extra transformer on your transformer stack. Those transformers are the types that need to + satisfy the requirements of your effect. Having 'MonadTrans' as a requirement is basically + free since each transformer has a 'MonadTrans' instance, kind of by definition. Anything + extra and things get a bit more complicated. To \"lift\" functions like 'forkIO' into other + transformers, people usually use the 'MonadTransControl' class from the "monad-control" + package. Pretty much all the standard transformers are instances of that class, with one + exception: 'ContT' isn't an instance of 'MonadTransControl'. 'ContT' is a pretty exotic + transformer though. + + What we're going to use here is the 'RunnableTrans' class. This is an alternative to the + 'MonadTransControl' class that's hopefully a bit easier to use. It lets us \"run\" a transformer + if we give it the right state value. It also lets us get the current state value from the context + and it can restore the context from the result of running the transformer. To cut through the + confusion (or perhaps to introduce more of it) here's the code: + +@ +data Fork +instance 'Effect' Fork where + data 'EffMethods' Fork m = ForkMethods + { _fork :: m () -> m ('Maybe' 'ThreadId') } + type 'CanLift' Fork t = 'RunnableTrans' t + 'mergeContext' mm = ForkMethods + (\\a -> do + ForkMethods m <- mm + m a) + 'liftThrough' (ForkMethods f) = ForkMethods + (\\a -> do + st <- 'currentTransState' + 'lift' (f ('void' ('runTransformer' a st))) + ) +@ + + Essentially what we do here is get the current state from the main computation (the one doing + the forking), using that state to run the forked computation, discard both its result /and/ + it's state using the 'void' function, then we finally call the original function and 'lift' the + whole thing. + + [Note] + Since we're discarding the result and the state of the forked computation, and this will + happen for each transformer/effect in the stack, it means that only effects that \"survive\" + are the final 'IO' ones. The state of the original computation does get shared with the + forked one, so that's pretty useful, but if we care about what the forked computation /did/ + with that state, we need to communicate with the original thread manually through some 'IO' + mechanism like 'MVar's. + + What about the effect handlers? How do we write one for our @Fork@ effect? Here's one that + ignores completely what the intended semantics were and just runs the thing sequentially: + +@ +implementForkSequentially :: Monad m => RuntimeImplemented Fork m a -> m a +implementForkSequentially = 'implement' (ForkMethods (\c -> c >> 'return' 'Nothing')) +@ + + But to /really/ fork the computation we have to use the original 'forkIO' function like this: + +@ +implementForkIO :: RuntimeImplemented Fork IO a -> IO a +implementForkIO = 'implement' (ForkMethods ('fmap' 'Just' . 'forkIO')) +@ + + Notice that this forces our monad to IO. This means no other effects can be handled after it. + This is manageable if @Fork@ is the only effect with that condition, but what if there are more + 'IO' based ones? A solution is to provide a @'MonadEffect' Fork IO@ instance directly + +@ +instance 'MonadEffect' Fork IO where + effect = ForkMethods ('fmap' 'Just' . 'liftIO') +@ + + Now we can write @implementForkIO@ like this + +@ +implementForkIO :: IO a -> IO a +implementForkIO = 'id' +@ + + As you can see, the function doesn't do anything, but it does force whatever we give it to be + in the 'IO' monad. This again means that we must handle this effect after all others, but if + there are other effects with the same requirement, they can all be handled at the end. +-}
+ src/Tutorial/Test.hs view
@@ -0,0 +1,113 @@+{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE MultiParamTypeClasses #-} +module Tutorial.Test where + +import Data.Text as T +import Data.Text.IO as T +import Control.Monad.IO.Class +import Control.Effects.State +import Control.Effects.List +import Control.Concurrent +import Control.Monad.Runnable +import Control.Monad.Trans +import Control.Monad + +addFruit :: (MonadIO m, MonadEffect (State [Text]) m) => m () +addFruit = do + liftIO (T.putStrLn "Name a type of fruit please") + fruit <- liftIO T.getLine + knownFruits <- getState + setState (fruit : knownFruits) + +main1 :: IO () +main1 = implementStateViaStateT @[Text] [] $ do + addFruit + addFruit + addFruit + fruits <- getState @[Text] + liftIO (print fruits) + +main2 :: IO () +main2 = + evaluateAll $ + implementStateViaStateT @[Text] [] $ do + addFruit + addFruit + addFruit + fruits <- getState @[Text] + fruit <- choose fruits + liftIO (print fruit) + +main3 :: IO () +main3 = do + evaluateAll $ + implementStateViaStateT @Int 0 $ do + setState @Int 1 + choose (Prelude.replicate 3 ()) + setState . succ =<< getState @Int + liftIO . print =<< getState @Int + implementStateViaStateT @Int 0 $ + evaluateAll $ do + setState @Int 1 + choose (Prelude.replicate 3 ()) + setState . succ =<< getState @Int + liftIO . print =<< getState @Int + +main4 :: IO () +main4 = do + lst <- evaluateToList $ + implementStateViaStateT @Int 0 $ do + setState @Int 1 + choose (Prelude.replicate 3 ()) + setState . succ =<< getState @Int + getState @Int + print lst + implementStateViaStateT @Int 0 $ do + evaluateAll $ do + setState @Int 1 + choose (Prelude.replicate 3 ()) + setState . succ =<< getState @Int + liftIO . print =<< getState @Int + +data Fork +instance Effect Fork where + data EffMethods Fork m = ForkMethods + { _fork :: m () -> m (Maybe ThreadId) } + type CanLift Fork t = RunnableTrans t + mergeContext mm = ForkMethods + (\a -> do + ForkMethods m <- mm + m a) + liftThrough (ForkMethods f) = ForkMethods + (\a -> do + st <- currentTransState + lift (f (void (runTransformer a st))) + ) + +instance MonadEffect Fork IO where + effect = ForkMethods (fmap Just . forkIO) + +fork :: MonadEffect Fork m => m () -> m (Maybe ThreadId) +fork = _fork effect + +testMethod :: (MonadEffects '[State Int, Fork] m, MonadIO m) => m () +testMethod = do + modifyState @Int (+10) + tid <- void $ fork $ do + liftIO . print =<< getState @Int + liftIO $ threadDelay 2000000 + modifyState @Int (+10) + liftIO . print =<< getState @Int + liftIO $ threadDelay 1000000 + liftIO $ print tid + modifyState @Int (+10) + liftIO . print =<< getState @Int + liftIO $ threadDelay 3000000 + liftIO . print =<< getState @Int + +main5 :: IO () +main5 = implementStateViaStateT @Int 0 testMethod
test/Main.hs view
@@ -1,4 +1,6 @@ {-# LANGUAGE NoMonomorphismRestriction, FlexibleContexts, ScopedTypeVariables, BangPatterns #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE TypeApplications #-} module Main where import Control.Monad.IO.Class @@ -7,13 +9,19 @@ import Control.Effects.State import Control.Effects.Parallel import Control.Effects.Early +import Control.Effects.Async +import Control.Effects.List +import Control.Effects.Yield +import Control.Concurrent hiding (yield) +import System.IO + import Data.Function -- Should infer ex1 = signal True -- Should compile -ex2 :: Throws Bool m => m () +ex2 :: MonadEffect (Throw Bool) m => m () ex2 = throwSignal False ex3 = do @@ -37,7 +45,7 @@ testEarly2 = handleEarly $ earlyReturn 'a' -orderTest :: (Handles Bool m, MonadEffect (State Int) m, MonadIO m) => m () +orderTest :: (MonadEffects '[HandleException Bool, Throw Bool, State Int] m, MonadIO m) => m () orderTest = do setState (1 :: Int) _ :: Either Bool () <- handleToEitherRecursive $ do @@ -59,14 +67,60 @@ main :: IO () main = do orderTest & handleException (\(_ :: Bool) -> return ()) - & handleStateT (0 :: Int) - orderTest & handleStateT (0 :: Int) + & implementStateViaStateT (0 :: Int) + orderTest & implementStateViaStateT (0 :: Int) & handleException (\(_ :: Bool) -> return ()) putStrLn "Starting sequential test" - replicateM_ 8 (handleStateT (0 :: Int) task >>= print) + replicateM_ 8 (implementStateViaStateT (0 :: Int) task >>= print) putStrLn "Sequential test done" putStrLn "Starting parallel test" - handleStateT (0 :: Int) $ do + implementStateViaStateT (0 :: Int) $ do res <- parallelWithSequence (replicate 8 task) mapM_ (liftIO . print) res putStrLn "Parallel test done" + +parallelTest :: + (MonadEffects '[Async, NonDeterminism] m, MonadIO m) => m (AsyncThread m (Int, Char)) +parallelTest = do + n <- choose [1,2,3,4] + async $ do + liftIO $ threadDelay ((5 - n) * 1000000) + l <- choose "ab" + return (n, l) + +mainAsync :: IO () +mainAsync = do + threads <- evaluateToList parallelTest + forM_ threads $ \thread -> + evaluateToList (do + p <- waitAsync thread + liftIO $ print p + ) + +yieldTest :: + (MonadEffects '[Yield Int, Async] m, MonadIO m) => m () +yieldTest = do + yield @Int 5 + t <- async $ do + liftIO $ putStrLn "yielding 6" + yield @Int 6 + liftIO $ putStrLn "yielding 10" + yield @Int 10 + + t2 <- async $ do + liftIO $ putStrLn "yielding 8" + yield @Int 8 + liftIO $ putStrLn "yielding 9" + yield @Int 9 + yield @Int 7 + waitAsync t + waitAsync t2 + return () + +mainYield :: IO () +mainYield = do + hSetBuffering stdout LineBuffering + await <- implementYieldViaMVar @Int yieldTest + traverseYielded_ await $ \res -> do + print res + void getLine