simple-effects 0.8.0.2 → 0.9.0.0
raw patch · 10 files changed
+176/−190 lines, 10 files
Files
- simple-effects.cabal +1/−2
- src/Control/Effects.hs +36/−26
- src/Control/Effects/Early.hs +39/−9
- src/Control/Effects/List.hs +13/−11
- src/Control/Effects/Parallel.hs +2/−2
- src/Control/Effects/Reader.hs +17/−9
- src/Control/Effects/Signal.hs +30/−33
- src/Control/Effects/State.hs +36/−37
- src/Control/Effects1.hs +0/−59
- test/Main.hs +2/−2
simple-effects.cabal view
@@ -1,5 +1,5 @@ name: simple-effects -version: 0.8.0.2 +version: 0.9.0.0 synopsis: A simple effect system that integrates with MTL description: Please see README.md homepage: https://gitlab.com/LukaHorvat/simple-effects @@ -14,7 +14,6 @@ library exposed-modules: Control.Effects - , Control.Effects1 , Control.Effects.State , Control.Effects.Reader , Control.Effects.List
src/Control/Effects.hs view
@@ -1,56 +1,66 @@ {-# LANGUAGE TypeFamilies, MultiParamTypeClasses, FlexibleInstances, DeriveFunctor , GeneralizedNewtypeDeriving, UndecidableInstances, StandaloneDeriving , IncoherentInstances #-} -module Control.Effects (module Control.Effects, module Control.Effects1) where +{-# LANGUAGE DataKinds, PolyKinds, TypeInType, Rank2Types, TypeOperators, ConstraintKinds #-} +module Control.Effects (module Control.Effects) where -import Import +import Import import Control.Monad.Runnable -import Control.Effects1 +import Data.Kind -type family EffectMsg eff :: * -type family EffectRes eff :: * +data MsgOrRes = Msg | Res +data family Effect (effKind :: Type) :: effKind -> MsgOrRes -> Type -class Monad m => MonadEffect eff m where - -- | Use the effect described by 'eff'. - effect :: proxy eff -> EffectMsg eff -> m (EffectRes eff) +class Monad m => MonadEffect effKind m where + -- | Use the effect described by 'method'. + effect :: Effect effKind method 'Msg -> m (Effect effKind method 'Res) +newtype EffectWithKind effKind m = EffectWithKind + { getEffectWithKind :: forall method. Effect effKind method 'Msg -> m (Effect effKind method 'Res) } + -- | The 'EffectHandler' is really just a 'ReaderT' carrying around the function that knows how to -- handle the effect. -newtype EffectHandler eff m a = EffectHandler - { unpackEffectHandler :: ReaderT (EffectMsg eff -> m (EffectRes eff)) m a } +newtype EffectHandler effKind m a = EffectHandler + { unpackEffectHandler :: ReaderT (EffectWithKind effKind m) m a } deriving ( Functor, Applicative, Monad, Alternative, MonadState s, MonadIO, MonadCatch , MonadThrow, MonadRandom ) -instance MonadTrans (EffectHandler eff) where +instance MonadTrans (EffectHandler effKind) where lift = EffectHandler . lift -instance RunnableTrans (EffectHandler eff) where - type TransformerState (EffectHandler eff) m = EffectMsg eff -> m (EffectRes eff) - type TransformerResult (EffectHandler eff) m a = a +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) -instance MonadReader s m => MonadReader s (EffectHandler eff m) where +instance MonadReader s m => MonadReader s (EffectHandler effKind m) where ask = EffectHandler (lift ask) local f (EffectHandler rdr) = EffectHandler (ReaderT $ local f . runReaderT rdr) -deriving instance MonadBase b m => MonadBase b (EffectHandler eff m) +deriving instance MonadBase b m => MonadBase b (EffectHandler effKind m) -instance MonadBaseControl b m => MonadBaseControl b (EffectHandler eff m) where - type StM (EffectHandler eff m) a = StM (ReaderT (EffectMsg eff -> m (EffectRes eff)) m) a +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 -instance {-# OVERLAPPABLE #-} (MonadEffect eff m, MonadTrans t, Monad (t m)) - => MonadEffect eff (t m) where +instance {-# OVERLAPPABLE #-} (MonadEffect method m, MonadTrans t, Monad (t m)) + => MonadEffect method (t m) where {-# INLINE effect #-} - effect p msg = lift (effect p msg) + effect msg = lift (effect msg) -instance Monad m => MonadEffect eff (EffectHandler eff m) where +instance Monad m => MonadEffect effKind (EffectHandler effKind m) where {-# INLINE effect #-} - effect _ msg = EffectHandler (ReaderT ($ msg)) + effect msg = EffectHandler (ReaderT (($ msg) . getEffectWithKind)) --- | Handle the effect described by 'eff'. -handleEffect :: (EffectMsg eff -> m (EffectRes eff)) -> EffectHandler eff m a -> m a -handleEffect f eh = runReaderT (unpackEffectHandler eh) f +-- | 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) + +type family MonadEffects effs m :: Constraint where + MonadEffects '[] m = () + MonadEffects (eff ': effs) m = (MonadEffect eff m, MonadEffects effs m)
src/Control/Effects/Early.hs view
@@ -1,5 +1,33 @@ {-# LANGUAGE RankNTypes, TypeFamilies, FlexibleContexts, ScopedTypeVariables, MultiParamTypeClasses #-} -{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE FlexibleInstances, DataKinds, GADTs #-} +-- | A neat effect that you can use to get early returns in your functions. Here's how to use it. +-- +-- Before: +-- +-- @ +-- f = do +-- m1 <- maybeFunc1 +-- case m1 of +-- Nothing -> return "1 nothing" +-- Just x -> do +-- m2 <- maybeFunc2 +-- case m2 of +-- Nothing -> return "2 nothing" +-- Just y -> return (x <> y) +-- @ +-- +-- After: +-- +-- @ +-- f = handleEarly $ do +-- m1 <- maybeFunc1 +-- x <- ifNothingEarlyReturn "1 nothing" m1 +-- m2 <- maybeFunc2 +-- y <- ifNothingEarlyReturn "2 nothing" m2 +-- return (x <> y) +-- @ +-- +-- You can use the 'earlyReturn' function directily, or one of the helpers for common use cases. module Control.Effects.Early ( module Control.Effects, Early , earlyReturn, handleEarly, onlyDo, ifNothingEarlyReturn, ifNothingDo @@ -9,21 +37,23 @@ import Control.Effects -newtype Early a = Early { getEarlyReturn :: a } -type instance EffectMsg (Early a) = a -type instance EffectRes (Early a) = Void +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 -instance (Monad m, a ~ b) => MonadEffect (Early a) (ExceptT (Early b) m) where - effect _ = throwE . Early +instance (Monad m, a ~ b) => MonadEffect (Early a) (ExceptT (EarlyValue b) m) where + effect (EarlyMsg a) = EarlyRes <$> throwE (EarlyValue a) -- | 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 = fmap (getEarlyReturn . absurd) . effect (Proxy :: Proxy (Early a)) +earlyReturn a = fmap (getEarlyValue . absurd . getEarlyRes) . effect $ EarlyMsg a -- | Get the result from a computation. Either the early returned one, or the regular result. -handleEarly :: Monad m => ExceptT (Early a) m a -> m a -handleEarly = fmap (either getEarlyReturn id) +handleEarly :: Monad m => ExceptT (EarlyValue a) m a -> m a +handleEarly = fmap (either getEarlyValue id) . runExceptT -- | Only do the given action and exit early with it's result.
src/Control/Effects/List.hs view
@@ -1,4 +1,6 @@ {-# LANGUAGE ScopedTypeVariables, TypeFamilies, FlexibleContexts, MultiParamTypeClasses #-} +{-# LANGUAGE DataKinds, GADTs #-} +-- | Add non-determinism to your monad. Uses the 'ListT' transformer under the hood. module Control.Effects.List ( module Control.Effects.List , module ListT ) where @@ -9,22 +11,22 @@ import ListT hiding (take) import Control.Effects - -data NonDeterministic +import Data.Kind -type instance EffectMsg1 NonDeterministic = [] -type instance EffectRes1 NonDeterministic = Identity -type instance EffectCon1 NonDeterministic a = () +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 -instance Monad m => MonadEffect1 NonDeterministic (ListT m) where - effect1 _ = fmap Identity . fromFoldable +instance Monad m => MonadEffect NonDeterministic (ListT m) where + effect (ChooseMsg list) = ChooseRes <$> fromFoldable list -- | Runs the rest of the computation for every value in the list -choose :: MonadEffect1 NonDeterministic m => [a] -> m a -choose = fmap runIdentity . effect1 (Proxy :: Proxy NonDeterministic) +choose :: MonadEffect NonDeterministic m => [a] -> m a +choose = fmap getChooseRes . effect . ChooseMsg -- | Signals that this branch of execution failed to produce a result. -deadEnd :: MonadEffect1 NonDeterministic m => m a +deadEnd :: MonadEffect NonDeterministic m => m a deadEnd = choose [] -- | Execute all the effects and collect the result in a list. @@ -42,7 +44,7 @@ foldAllResults :: Monad m => (r -> a -> m r) -> r -> ListT m a -> m r foldAllResults = fold --- | Same as 'foldAllResults' but the folding function has the ability to terminate eary by +-- | Same as 'foldAllResults' but the folding function has the ability to terminate early by -- returning Nothing. foldWithEarlyTermination :: Monad m => (r -> a -> m (Maybe r)) -> r -> ListT m a -> m r foldWithEarlyTermination = foldMaybe
src/Control/Effects/Parallel.hs view
@@ -1,7 +1,7 @@ {-# LANGUAGE ScopedTypeVariables, FlexibleContexts #-} module Control.Effects.Parallel where -import Import +import Import hiding (State) import GHC.MVar import GHC.IO.Unsafe @@ -17,7 +17,7 @@ _ <- forkFinally proc (\_ -> putMVar h ()) return h -appendState :: forall s m a proxy. (Semigroup s, MonadEffectState s m) +appendState :: forall s m a proxy. (Semigroup s, MonadEffect (State s) m) => proxy s -> m a -> m a appendState _ m = do s :: s <- getState
src/Control/Effects/Reader.hs view
@@ -1,20 +1,28 @@ {-# LANGUAGE ScopedTypeVariables, TypeFamilies, FlexibleContexts #-} +{-# LANGUAGE DataKinds, GADTs #-} +-- | 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 +-- TypeApplications. +-- +-- 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 +-- which you can provide a different environment implementation _at runtime_. module Control.Effects.Reader (module Control.Effects.Reader, module Control.Effects) where -import Import - import Control.Effects -data ReadEnv e - -type instance EffectMsg (ReadEnv e) = () -type instance EffectRes (ReadEnv e) = e +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 readEnv :: forall e m. MonadEffect (ReadEnv e) m => m e -readEnv = effect (Proxy :: Proxy (ReadEnv e)) () +readEnv = getReadEnvRes <$> effect ReadEnvMsg -handleReadEnv :: m e -> EffectHandler (ReadEnv e) m a -> m a -handleReadEnv = handleEffect . const +handleReadEnv :: Functor m => m e -> EffectHandler (ReadEnv e) m a -> m a +handleReadEnv m = handleEffect (\ReadEnvMsg -> ReadEnvRes <$> m) handleSubreader :: MonadEffect (ReadEnv e) m => (e -> e') -> EffectHandler (ReadEnv e') m a -> m a handleSubreader f = handleReadEnv (f <$> readEnv)
src/Control/Effects/Signal.hs view
@@ -1,12 +1,17 @@ {-# LANGUAGE TypeFamilies, ScopedTypeVariables, FlexibleContexts, Rank2Types, ConstraintKinds #-} -{-# LANGUAGE DeriveAnyClass, OverloadedStrings, MultiParamTypeClasses, NoMonomorphismRestriction #-} +{-# LANGUAGE MultiParamTypeClasses, NoMonomorphismRestriction #-} {-# LANGUAGE FlexibleInstances, UndecidableInstances, DataKinds, TypeOperators #-} +{-# LANGUAGE GADTs, TypeApplications #-} {-# 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 +-- will be the result /of calling the 'signal' function/. This effectively allows you to have +-- recoverable exceptions at the throw site, instead of just at the handling site. module Control.Effects.Signal - ( MonadEffectSignal(..), ResumeOrBreak(..), throwSignal, handleSignal + ( ResumeOrBreak(..), Signal, throwSignal, handleSignal , Throws, handleException, handleToEither, module Control.Effects , module Control.Monad.Trans.Except, MaybeT(..), discardAllExceptions, showAllExceptions - , Handles(..), handleToEitherRecursive, SomeSignal ) where + , Handles(..), handleToEitherRecursive, SomeSignal, signal ) where import Import import Control.Monad.Trans.Except @@ -15,11 +20,12 @@ import Control.Effects import Control.Monad.Runnable -data Signal a b -type instance EffectMsg (Signal a b) = a -type instance EffectRes (Signal a b) = b +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 SomeSignal = SomeSignal { getSomeSignal :: Text } deriving (Eq, Ord, Read, Show) +newtype SomeSignal = SomeSignal { getSomeSignal :: Text } deriving (Eq, Ord, Read, Show) type family UnhandledError a b :: ErrorMessage where UnhandledError a Void = @@ -31,35 +37,21 @@ ':$$: '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 _ = throwE + effect (SignalMsg a) = throwE a instance (Show e, Monad m) => MonadEffect (Signal e b) (ExceptT SomeSignal m) where - effect _ = throwE . SomeSignal . pack . show + effect (SignalMsg a) = throwE . SomeSignal . pack . show $ a instance Monad m => MonadEffect (Signal a b) (MaybeT m) where - effect _ _ = mzero + effect _ = mzero instance TypeError (UnhandledError a b) => MonadEffect (Signal a b) IO where effect = undefined --- | This class 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 --- will be the result /of calling the 'signal' function/. This effectively allows you to have --- recoverable exceptions at the call site, instead of just at the handling site. --- --- This class can be considered an alias for @'MonadEffect' ('Signal' a b)@ so your code isn't --- required to provide any instances. -class MonadEffect (Signal a b) m => MonadEffectSignal a b m where - -- | There are no restrictions on the type of values that can be thrown or returned. - signal :: a -> m b - signal = effect (Proxy :: Proxy (Signal a b)) +instance (Monad m, b ~ c) => MonadEffect (Signal a c) (EffectHandler (Signal a b) m) where + effect = effect @(Signal a b) -type Throws e m = MonadEffectSignal e Void m +signal :: MonadEffect (Signal a b) m => a -> m b +signal a = getSignalRes <$> effect (SignalMsg a) -instance (Monad m, b ~ c) => MonadEffectSignal a c (EffectHandler (Signal a b) m) -instance Monad m => MonadEffectSignal a b (MaybeT m) -instance {-# OVERLAPPABLE #-} Monad m => MonadEffectSignal e b (ExceptT e m) -instance (Monad m, Show e) => MonadEffectSignal e b (ExceptT SomeSignal m) -instance MonadEffect (Signal a b) IO => MonadEffectSignal a b IO -instance {-# OVERLAPPABLE #-} (MonadEffectSignal a b m, MonadTrans t, Monad (t m)) - => MonadEffectSignal a b (t m) +type Throws e m = MonadEffect (Signal e Void) m -- | 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. @@ -85,13 +77,18 @@ -- | Handle signals of a computation. The handler function has the option to provide a value -- to the caller of 'signal' and continue execution there, or do what regular exception handlers -- do and continue execution after the handler. -handleSignal :: Monad m +handleSignal :: forall a b c m. Monad m => (a -> m (ResumeOrBreak b c)) -> EffectHandler (Signal a b) (ExceptT c m) c -> m c handleSignal f = fmap collapseEither . runExceptT - . handleEffect (resumeOrBreak return throwE <=< lift . f) + . handleEffect h + where + h :: forall method. Effect (Signal a b) method 'Msg -> ExceptT c m (Effect (Signal a b) method 'Res) + h (SignalMsg a) = do + rb <- lift (f a) + resumeOrBreak (return . SignalRes) throwE rb -- | This handler can only behave like a regular exception handler. If used along with 'throwSignal' -- this module behaves like regular checked exceptions. @@ -102,7 +99,7 @@ handleToEither :: ExceptT e m a -> m (Either e a) handleToEither = runExceptT --- | Discard all the 'Throws' and 'MonadEffectSignal' constraints. If any exception was thrown +-- | Discard all the 'Throws' and 'Signal' constraints. If any exception was thrown -- the result will be 'Nothing'. discardAllExceptions :: MaybeT m a -> m (Maybe a) discardAllExceptions = runMaybeT @@ -111,7 +108,7 @@ mapLeft f (Left a) = Left (f a) mapLeft _ (Right b) = Right b --- | Satisfies all the 'Throws' and 'MonadEffectSignal' constraints /if/ they all throw 'Show'able +-- | Satisfies all the 'Throws' 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
src/Control/Effects/State.hs view
@@ -1,67 +1,66 @@ {-# LANGUAGE TypeFamilies, ScopedTypeVariables, FlexibleContexts, Rank2Types, ConstraintKinds #-} {-# LANGUAGE MultiParamTypeClasses, GADTs #-} -module Control.Effects.State (module Control.Effects.State, module Control.Effects1) where +{-# LANGUAGE DataKinds, TypeInType #-} +-- | 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 +-- TypeApplications. +-- +-- 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 +-- 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 Control.Effects1 - -data State s -data Get -data Set -data StateMessage s a where - GetMessage :: StateMessage s Get - SetMessage :: !s -> StateMessage s Set -data StateResult s a where - GetResult :: { getGetResult :: !s } -> StateResult s Get - SetResult :: StateResult s Set - -type instance EffectMsg1 (State s) = StateMessage s -type instance EffectRes1 (State s) = StateResult s -type instance EffectCon1 (State s) a = () - -instance Monad m => MonadEffect1 (State s) (StateT s m) where - effect1 _ GetMessage = GetResult <$> get - effect1 _ (SetMessage s) = SetResult <$ put s - {-# INLINE effect1 #-} +import Control.Effects -type MonadEffectState s m = MonadEffect1 (State s) m +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 -stateEffect :: forall s a m. MonadEffectState s m - => StateMessage s a -> m (StateResult s a) -stateEffect = effect1 (Proxy :: Proxy (State s)) -{-# INLINE stateEffect #-} +instance Monad m => MonadEffect (State s) (StateT s m) where + effect GetStateMsg = GetStateRes <$> get + effect (SetStateMsg s) = SetStateRes <$ put s + {-# INLINE effect #-} -getState :: forall s m. MonadEffectState s m => m s -getState = getGetResult <$> stateEffect GetMessage +getState :: forall s m. MonadEffect (State s) m => m s +getState = getGetStateRes <$> effect GetStateMsg {-# INLINE getState #-} -setState :: forall s m. MonadEffectState s m => s -> m () -setState s = void $ stateEffect (SetMessage s) +setState :: forall s m. MonadEffect (State s) m => s -> m () +setState s = void $ effect (SetStateMsg s) {-# INLINE setState #-} -modifyState :: forall s m. MonadEffectState s m => (s -> s) -> m () +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 #-} +-- | Handle the 'MonadEffect (State s)' constraint by providing custom handling functions. handleState :: forall m s a. Monad m => m s -> (s -> m ()) - -> EffectHandler1 (State s) m a -> m a + -> EffectHandler (State s) m a -> m a handleState getter setter = - handleEffect1 handler - where handler :: forall b. StateMessage s b -> m (StateResult s b) - handler GetMessage = GetResult <$> getter - handler (SetMessage s) = SetResult <$ setter s + 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 #-} -handleStateIO :: MonadIO m => s -> EffectHandler1 (State s) m a -> m a +-- | Handle the state requirement using an 'IORef'. +handleStateIO :: MonadIO m => s -> EffectHandler (State s) m a -> m a handleStateIO initial m = do ref <- liftIO (newIORef initial) m & handleState (liftIO (readIORef ref)) (liftIO . writeIORef ref) {-# INLINE handleStateIO #-} +-- | 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/Effects1.hs
@@ -1,59 +0,0 @@-{-# LANGUAGE TypeFamilies, MultiParamTypeClasses, FlexibleInstances, DeriveFunctor - , GeneralizedNewtypeDeriving, UndecidableInstances, StandaloneDeriving - , IncoherentInstances, RankNTypes, ConstraintKinds #-} -module Control.Effects1 where - -import Import - -import Control.Monad.Runnable - -type family EffectMsg1 eff :: * -> * -type family EffectRes1 eff :: * -> * -type family EffectCon1 eff a :: Constraint - -class Monad m => MonadEffect1 eff m where - -- | Use the effect described by 'eff'. - effect1 :: EffectCon1 eff a => proxy eff -> EffectMsg1 eff a -> m (EffectRes1 eff a) - -newtype EffHandling1 eff m = EffHandling1 { - getHandling1 :: forall a. EffectCon1 eff a => EffectMsg1 eff a -> m (EffectRes1 eff a) } - --- | The 'EffectHandler1' is really just a 'ReaderT' carrying around the function that knows how to --- handle the effect. -newtype EffectHandler1 eff m a = EffectHandler1 - { unpackEffectHandler1 :: ReaderT (EffHandling1 eff m) m a } - deriving ( Functor, Applicative, Monad, Alternative, MonadState s, MonadIO, MonadCatch - , MonadThrow, MonadRandom ) - -instance MonadTrans (EffectHandler1 eff) where - lift = EffectHandler1 . lift - -instance RunnableTrans (EffectHandler1 eff) where - type TransformerState (EffectHandler1 eff) m = EffHandling1 eff m - type TransformerResult (EffectHandler1 eff) m a = a - currentTransState = EffectHandler1 ask - restoreTransState = return - runTransformer m = runReaderT (unpackEffectHandler1 m) - -instance MonadReader s m => MonadReader s (EffectHandler1 eff m) where - ask = EffectHandler1 (lift ask) - local f (EffectHandler1 rdr) = EffectHandler1 (ReaderT $ local f . runReaderT rdr) - -deriving instance MonadBase IO m => MonadBase IO (EffectHandler1 eff m) - -instance MonadBaseControl IO m => MonadBaseControl IO (EffectHandler1 eff m) where - type StM (EffectHandler1 eff m) a = StM (ReaderT (EffHandling1 eff m) m) a - liftBaseWith f = EffectHandler1 $ liftBaseWith $ \q -> f (q . unpackEffectHandler1) - restoreM = EffectHandler1 . restoreM - -instance {-# OVERLAPPABLE #-} (MonadEffect1 eff m, MonadTrans t, Monad (t m)) - => MonadEffect1 eff (t m) where - effect1 p msg = lift (effect1 p msg) - -instance Monad m => MonadEffect1 eff (EffectHandler1 eff m) where - effect1 _ msg = EffectHandler1 (ReaderT (($ msg) . getHandling1)) - --- | Handle the effect described by 'eff'. -handleEffect1 :: (forall a. EffectCon1 eff a => EffectMsg1 eff a -> m (EffectRes1 eff a)) - -> EffectHandler1 eff m b -> m b -handleEffect1 f eh = runReaderT (unpackEffectHandler1 eh) (EffHandling1 f)
test/Main.hs view
@@ -37,7 +37,7 @@ testEarly2 = handleEarly $ earlyReturn 'a' -orderTest :: (Handles Bool m, MonadEffectState Int m, MonadIO m) => m () +orderTest :: (Handles Bool m, MonadEffect (State Int) m, MonadIO m) => m () orderTest = do setState (1 :: Int) _ :: Either Bool () <- handleToEitherRecursive $ do @@ -50,7 +50,7 @@ inc :: Int -> Int inc !x = x + 1 -task :: (MonadEffectState Int m) => m Int +task :: (MonadEffect (State Int) m) => m Int task = do replicateM_ 10000000 (modifyState inc) st <- getState