packages feed

polysemy-zoo 0.3.0.0 → 0.4.0.0

raw patch · 22 files changed

+1048/−43 lines, 22 filesdep +asyncdep +binarydep +bytestringdep −textdep ~basedep ~containersdep ~hspecPVP ok

version bump matches the API change (PVP)

Dependencies added: async, binary, bytestring, ghc-prim, hedis

Dependencies removed: text

Dependency ranges changed: base, containers, hspec, polysemy, polysemy-plugin

API changes (from Hackage documentation)

+ Polysemy.Alias: type InterpreterOf e r = forall x. Sem (e : r) x -> Sem r x
+ Polysemy.Final: [WithWeaving] :: (forall f. Functor f => f () -> (forall x. f (z x) -> m (f x)) -> (forall x. f x -> Maybe x) -> m (f a)) -> Final m z a
+ Polysemy.Final: bindS :: Monad m => (a -> n b) -> Sem (WithStrategy m f n) (f a -> m (f b))
+ Polysemy.Final: data Final m z a
+ Polysemy.Final: embedFinal :: Functor m => Member (Final m) r => m a -> Sem r a
+ Polysemy.Final: getInitialStateS :: Sem (WithStrategy m f n) (f ())
+ Polysemy.Final: getInspectorS :: Sem (WithStrategy m f n) (Inspector f)
+ Polysemy.Final: interpretFinal :: forall e m r a. (Member (Final m) r, Functor m) => (forall x n. e n x -> Strategic m n x) -> Sem (e : r) a -> Sem r a
+ Polysemy.Final: liftS :: Functor m => m a -> Strategic m n a
+ Polysemy.Final: pureS :: Applicative m => a -> Strategic m n a
+ Polysemy.Final: runFinal :: Monad m => Sem '[Final m, Lift m] a -> m a
+ Polysemy.Final: runFinalLift :: Monad m => (forall x. n x -> m x) -> Sem [Final m, Lift m, Lift n] a -> m a
+ Polysemy.Final: runFinalLiftIO :: MonadIO m => Sem [Final m, Lift m, Lift IO] a -> m a
+ Polysemy.Final: runS :: Monad m => n a -> Sem (WithStrategy m f n) (m (f a))
+ Polysemy.Final: type Strategic m n a = forall f. Functor f => Sem (WithStrategy m f n) (m (f a))
+ Polysemy.Final: type WithStrategy m f n = WithTactics (Lift m) f n '[]
+ Polysemy.Final: withStrategic :: Member (Final m) r => Strategic m (Sem r) a -> Sem r a
+ Polysemy.Final: withWeaving :: forall m a r. Member (Final m) r => (forall f. Functor f => f () -> (forall x. f (Sem r x) -> m (f x)) -> (forall x. f x -> Maybe x) -> m (f a)) -> Sem r a
+ Polysemy.Final.Async: runAsyncFinal :: Member (Final IO) r => Sem (Async : r) a -> Sem r a
+ Polysemy.Final.Error: instance Data.Typeable.Internal.Typeable e => GHC.Exception.Type.Exception (Polysemy.Final.Error.WrappedExc e)
+ Polysemy.Final.Error: instance Data.Typeable.Internal.Typeable e => GHC.Show.Show (Polysemy.Final.Error.WrappedExc e)
+ Polysemy.Final.Error: runErrorInIOFinal :: (Typeable e, Member (Final IO) r) => Sem (Error e : r) a -> Sem r (Either e a)
+ Polysemy.Final.Fixpoint: runFixpointFinal :: (Member (Final m) r, MonadFix m) => Sem (Fixpoint : r) a -> Sem r a
+ Polysemy.Final.MTL: runErrorFinal :: (Member (Final m) r, MonadError e m) => Sem (Error e : r) a -> Sem r a
+ Polysemy.Final.MTL: runReaderFinal :: (Member (Final m) r, MonadReader i m) => Sem (Reader i : r) a -> Sem r a
+ Polysemy.Final.MTL: runStateFinal :: (Member (Lift m) r, MonadState s m) => Sem (State s : r) a -> Sem r a
+ Polysemy.Final.MTL: runWriterFinal :: (Member (Final m) r, MonadWriter o m) => Sem (Writer o : r) a -> Sem r a
+ Polysemy.Final.Resource: runResourceFinal :: Member (Final IO) r => Sem (Resource : r) a -> Sem r a
+ Polysemy.Floodgate: [Hold] :: m () -> Floodgate m ()
+ Polysemy.Floodgate: [Release] :: Floodgate m ()
+ Polysemy.Floodgate: data Floodgate m a
+ Polysemy.Floodgate: hold :: forall r_avmO. MemberWithError Floodgate r_avmO => Sem r_avmO () -> Sem r_avmO ()
+ Polysemy.Floodgate: release :: forall r_avmQ. MemberWithError Floodgate r_avmQ => Sem r_avmQ ()
+ Polysemy.Floodgate: runFloodgate :: Sem (Floodgate : r) a -> Sem r a
+ Polysemy.Floodgate: runFloodgateDry :: Sem (Floodgate : r) a -> Sem r a
+ Polysemy.KVStore: existsKV :: Member (KVStore k v) r => k -> Sem r Bool
+ Polysemy.KVStore: lookupOrThrowKV :: Members '[KVStore k v, Error e] r => (k -> e) -> k -> Sem r v
+ Polysemy.KVStore: modifyKV :: Member (KVStore k v) r => v -> (v -> v) -> k -> Sem r ()
+ Polysemy.KVStore: runKVStoreInRedis :: (Member (Lift Redis) r, Member (Error Reply) r, Binary k, Binary v) => (k -> ByteString) -> Sem (KVStore k v : r) a -> Sem r a
+ Polysemy.Redis.Utils: Path :: ByteString -> Path
+ Polysemy.Redis.Utils: [getPath] :: Path -> ByteString
+ Polysemy.Redis.Utils: getFromRedis :: Binary a => ByteString -> a
+ Polysemy.Redis.Utils: instance Data.String.IsString Polysemy.Redis.Utils.Path
+ Polysemy.Redis.Utils: instance GHC.Classes.Eq Polysemy.Redis.Utils.Path
+ Polysemy.Redis.Utils: instance GHC.Classes.Ord Polysemy.Redis.Utils.Path
+ Polysemy.Redis.Utils: instance GHC.Show.Show Polysemy.Redis.Utils.Path
+ Polysemy.Redis.Utils: newtype Path
+ Polysemy.Redis.Utils: putForRedis :: Binary a => a -> ByteString
+ Polysemy.SetStore: [AddS] :: k -> v -> SetStore k v m ()
+ Polysemy.SetStore: [DelS] :: k -> v -> SetStore k v m ()
+ Polysemy.SetStore: [MemberS] :: k -> v -> SetStore k v m Bool
+ Polysemy.SetStore: addS :: forall k_aDVm v_aDVn r_aDYm. MemberWithError (SetStore k_aDVm v_aDVn) r_aDYm => k_aDVm -> v_aDVn -> Sem r_aDYm ()
+ Polysemy.SetStore: data SetStore k v m a
+ Polysemy.SetStore: delS :: forall k_aDVm v_aDVn r_aDYp. MemberWithError (SetStore k_aDVm v_aDVn) r_aDYp => k_aDVm -> v_aDVn -> Sem r_aDYp ()
+ Polysemy.SetStore: memberS :: forall k_aDVm v_aDVn r_aDYs. MemberWithError (SetStore k_aDVm v_aDVn) r_aDYs => k_aDVm -> v_aDVn -> Sem r_aDYs Bool
+ Polysemy.SetStore: runSetStoreAsKVStore :: (Member (KVStore k (Set v)) r, Ord v) => InterpreterOf (SetStore k v) r
+ Polysemy.SetStore: runSetStoreInRedis :: (Member (Lift Redis) r, Member (Error Reply) r, Binary k, Binary v) => (k -> Path) -> InterpreterOf (SetStore k v) r
- Polysemy.KVStore: lookupKV :: forall k_ahpZ v_ahq0 r_ak5w. Member (KVStore k_ahpZ v_ahq0) r_ak5w => k_ahpZ -> Sem r_ak5w (Maybe v_ahq0)
+ Polysemy.KVStore: lookupKV :: forall k_aCFc v_aCFd r_aCHa. MemberWithError (KVStore k_aCFc v_aCFd) r_aCHa => k_aCFc -> Sem r_aCHa (Maybe v_aCFd)
- Polysemy.KVStore: updateKV :: forall k_ahpZ v_ahq0 r_ak5x. Member (KVStore k_ahpZ v_ahq0) r_ak5x => k_ahpZ -> Maybe v_ahq0 -> Sem r_ak5x ()
+ Polysemy.KVStore: updateKV :: forall k_aCFc v_aCFd r_aCHc. MemberWithError (KVStore k_aCFc v_aCFd) r_aCHc => k_aCFc -> Maybe v_aCFd -> Sem r_aCHc ()
- Polysemy.Random: random :: forall r_aokg a_aoiv. (Member Random r_aokg, Random a_aoiv) => Sem r_aokg a_aoiv
+ Polysemy.Random: random :: forall r_ax9l a_ax7A. (MemberWithError Random r_ax9l, Random a_ax7A) => Sem r_ax9l a_ax7A
- Polysemy.Random: randomR :: forall r_aokh a_aoiv. (Member Random r_aokh, Random a_aoiv) => (a_aoiv, a_aoiv) -> Sem r_aokh a_aoiv
+ Polysemy.Random: randomR :: forall r_ax9m a_ax7A. (MemberWithError Random r_ax9m, Random a_ax7A) => (a_ax7A, a_ax7A) -> Sem r_ax9m a_ax7A
- Polysemy.Several: runSeveral :: (forall r k x. k -> Sem (e k : r) x -> Sem r x) -> HList t -> Sem (TypeConcat (TypeMap e t) r) a -> Sem r a
+ Polysemy.Several: runSeveral :: (forall r' k x. k -> Sem (e k : r') x -> Sem r' x) -> HList t -> Sem (TypeConcat (TypeMap e t) r) a -> Sem r a

Files

ChangeLog.md view
@@ -1,5 +1,18 @@ # Changelog for polysemy-zoo +## 0.4.0.0 (2019-07-10)++### Breaking Changes++- The semantics of `absorbWriter` are now aligned with the `MTL` implementation++### New Effects and Interpreters++- Added `SetStore` effect+- Added `Floodgate` effect+- Added `lookupOrThrowKV`, `existsKV` and `modifyKV` actions to `KVStore`+- Added Redis interpretations of `SetStore` and `KVStore`+ ## 0.3.0.0 (2019-06-17)  - Removed `Polysemy.MTL`@@ -31,3 +44,5 @@  ## Unreleased changes +* In AbsorbMonadWriter, re-implemented mtl pass in terms of the new Polysemy.Writer+pass and confirmed all tests.
README.md view
@@ -1,6 +1,6 @@ # polysemy-zoo -[![Build Status](https://api.travis-ci.org/isovector/polysemy-zoo.svg?branch=master)](https://travis-ci.org/isovector/polysemy-zoo)+[![Build Status](https://api.travis-ci.org/polysemy-research/polysemy-zoo.svg?branch=master)](https://travis-ci.org/polysemy-research/polysemy-zoo) [![Hackage](https://img.shields.io/hackage/v/polysemy-zoo.svg?logo=haskell)](https://hackage.haskell.org/package/polysemy-zoo)  ## Dedication
polysemy-zoo.cabal view
@@ -4,10 +4,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: 4b8fa26e65b475e244f147260389f8556a05486cc6f16601fe586ce85a9a33f0+-- hash: 2a632f7d2c785cf339754bf2cf1895a7996a6b348cc8704e54e6b45769f39d91  name:           polysemy-zoo-version:        0.3.0.0+version:        0.4.0.0 synopsis:       Experimental, user-contributed effects and interpreters for polysemy description:    Please see the README on GitHub at <https://github.com/isovector/polysemy-zoo#readme> category:       Polysemy@@ -29,29 +29,44 @@  library   exposed-modules:+      Polysemy.Alias       Polysemy.ConstraintAbsorber       Polysemy.ConstraintAbsorber.MonadError       Polysemy.ConstraintAbsorber.MonadReader       Polysemy.ConstraintAbsorber.MonadState       Polysemy.ConstraintAbsorber.MonadWriter+      Polysemy.Final+      Polysemy.Final.Async+      Polysemy.Final.Error+      Polysemy.Final.Fixpoint+      Polysemy.Final.MTL+      Polysemy.Final.Resource+      Polysemy.Floodgate       Polysemy.IdempotentLowering       Polysemy.KVStore       Polysemy.Operators       Polysemy.Random+      Polysemy.Redis.Utils+      Polysemy.SetStore       Polysemy.Several   other-modules:       Paths_polysemy_zoo   hs-source-dirs:       src   default-extensions: DataKinds DeriveFunctor FlexibleContexts GADTs LambdaCase PolyKinds RankNTypes ScopedTypeVariables StandaloneDeriving TypeApplications TypeOperators TypeFamilies UnicodeSyntax-  ghc-options: -fplugin=Polysemy.Plugin+  ghc-options: -fplugin=Polysemy.Plugin -Wall   build-depends:-      base >=4.7 && <5+      async >=2.2 && <3+    , base >=4.9 && <5+    , binary >=0.8.5.1 && <0.9+    , bytestring >=0.10.8.2 && <0.11     , constraints >=0.10.1 && <0.12-    , containers >=0.6 && <0.7+    , containers >=0.5 && <0.7+    , ghc-prim >=0.5.2 && <0.6+    , hedis >=0.10 && <0.13     , mtl >=2.0.1.0 && <3.0.0.0-    , polysemy >=0.4-    , polysemy-plugin+    , polysemy >=0.7.0.0+    , polysemy-plugin >=0.2     , random >=1.1 && <1.2     , reflection >=2.1.4 && <3.0.0   default-language: Haskell2010@@ -61,6 +76,8 @@   main-is: Main.hs   other-modules:       ConstraintAbsorberSpec+      FinalSpec+      FloodgateSpec       IdempotentLoweringSpec       KVStoreSpec       SeveralSpec@@ -68,19 +85,23 @@   hs-source-dirs:       test   default-extensions: DataKinds DeriveFunctor FlexibleContexts GADTs LambdaCase PolyKinds RankNTypes ScopedTypeVariables StandaloneDeriving TypeApplications TypeOperators TypeFamilies UnicodeSyntax-  ghc-options: -fplugin=Polysemy.Plugin -threaded -rtsopts -with-rtsopts=-N+  ghc-options: -fplugin=Polysemy.Plugin -Wall -threaded -rtsopts -with-rtsopts=-N   build-tool-depends:       hspec-discover:hspec-discover >=2.0   build-depends:-      base >=4.7 && <5+      async >=2.2 && <3+    , base >=4.9 && <5+    , binary >=0.8.5.1 && <0.9+    , bytestring >=0.10.8.2 && <0.11     , constraints >=0.10.1 && <0.12-    , containers >=0.6 && <0.7-    , hspec+    , containers >=0.5 && <0.7+    , ghc-prim >=0.5.2 && <0.6+    , hedis >=0.10 && <0.13+    , hspec >=2.6.0 && <3     , mtl >=2.0.1.0 && <3.0.0.0-    , polysemy >=0.4-    , polysemy-plugin+    , polysemy >=0.7.0.0+    , polysemy-plugin >=0.2     , polysemy-zoo     , random >=1.1 && <1.2     , reflection >=2.1.4 && <3.0.0-    , text   default-language: Haskell2010
+ src/Polysemy/Alias.hs view
@@ -0,0 +1,6 @@+module Polysemy.Alias where++import Polysemy++type InterpreterOf e r = forall x.  Sem (e ': r) x -> Sem r x+
src/Polysemy/ConstraintAbsorber/MonadState.hs view
@@ -44,7 +44,7 @@  -- Locally defined so that the instance we are going to build with reflection -- must be coherent, that is there cannot be orphans.-newtype Action m s' a = Action { action :: m a }+newtype Action m s' a = Action (m a)   deriving (Functor, Applicative, Monad)  
src/Polysemy/ConstraintAbsorber/MonadWriter.hs view
@@ -28,13 +28,12 @@        -- 'Sem'. This might be something with type @'S.MonadWriter' w m => m a@.     -> Sem r a absorbWriter =-  let semTell = tell+  let swapTuple (x,y) = (y,x)+      semTell = tell       semListen :: Member (Writer w) r => Sem r b -> Sem r (b, w)-      semListen = fmap (\(x,y) -> (y,x)) . listen @w-      semPass ::  Member (Writer w) r => Sem r (b, w -> w) -> Sem r b-      semPass x = do-        (w, (a, f)) <- listen x-        censor f (tell w >> pure a)+      semListen = fmap swapTuple . listen @w+      semPass :: Member (Writer w) r => Sem r (b, w -> w) -> Sem r b+      semPass = pass @w . fmap swapTuple   in absorbWithSem @(S.MonadWriter _) @Action      (WriterDict semTell semListen semPass)      (Sub Dict)
+ src/Polysemy/Final.hs view
@@ -0,0 +1,276 @@+{-# LANGUAGE TemplateHaskell #-}+module Polysemy.Final+  (+    -- * Effect+    Final(..)++    -- * Actions+  , withWeaving+  , withStrategic+  , embedFinal++    -- * Combinators for Interpreting to the Final Monad+  , interpretFinal++    -- * Strategy+    -- | Strategy is a domain-specific language very similar to @Tactics@+    -- (see 'Tactical'), and is used to describe how higher-order effects+    -- are threaded down to the final monad.+    --+    -- Much like @Tactics@, computations can be run and threaded+    -- through the use of 'runS' and 'bindS', and first-order constructors+    -- may use 'pureS'. In addition, 'liftS' may be used to+    -- lift actions of the final monad.+    --+    -- Unlike @Tactics@, the final return value within a `Strategic`+    -- must be a monadic value of the target monad+    -- with the functorial state wrapped inside of it.+  , Strategic+  , WithStrategy+  , pureS+  , liftS+  , runS+  , bindS+  , getInspectorS+  , getInitialStateS++    -- * Interpretations+  , runFinal+  , runFinalLift+  , runFinalLiftIO+  ) where++import Data.Functor.Identity++import Polysemy+import Data.Functor.Compose+import Polysemy.Internal+import Polysemy.Internal.Tactics+import Polysemy.Internal.Union+import Control.Monad+import Control.Monad.IO.Class++-----------------------------------------------------------------------------+-- | An effect for embedding higher-order effects in the final target monad+-- of the effect stack.+--+-- This is very useful for writing interpreters that interpret higher-order+-- effects in terms of the final monad - however, these interpreters+-- are subject to very different semantics than regular ones.+-- For more information, see 'interpretFinal'.+data Final m z a where+  WithWeaving :: (forall f.+                      Functor f+                   => f ()+                   -> (forall x. f (z x) -> m (f x))+                   -> (forall x. f x -> Maybe x)+                   -> m (f a)+                 )+              -> Final m z a++makeSem_ ''Final++-----------------------------------------------------------------------------+-- | Allows for embedding higher-order actions of the final monad+-- by providing the means of explicitly threading effects through 'Sem r'+-- to the final monad. Consider using 'withStrategic' instead,+-- as it provides a more user-friendly interface to the same power.+--+-- You are discouraged from using 'withWeaving' directly in application code,+-- as it ties your application code directly to the underlying monad.+withWeaving :: forall m a r+            .   Member (Final m) r+            => (forall f.+                    Functor f+                 => f ()+                 -> (forall x. f (Sem r x) -> m (f x))+                 -> (forall x. f x -> Maybe x)+                 -> m (f a)+               )+            -> Sem r a++-----------------------------------------------------------------------------+-- | 'withWeaving' admits an implementation of 'sendM'.+--+-- Just like 'sendM', you are discouraged from using this in application code.+embedFinal :: Functor m => Member (Final m) r => m a -> Sem r a+embedFinal m = withWeaving $ \s _ _ -> (<$ s) <$> m+++-----------------------------------------------------------------------------+-- | Allows for embedding higher-order actions of the final monad+-- by providing the means of explicitly threading effects through 'Sem r'+-- to the final monad. This is done through the use of the 'Strategic'+-- environment.+--+-- You are discouraged from using 'withStrategic' in application code,+-- as it ties your application code directly to the underlying monad.+withStrategic :: Member (Final m) r => Strategic m (Sem r) a -> Sem r a+withStrategic strat = withWeaving $ \s wv ins -> runStrategy s wv ins strat++------------------------------------------------------------------------------+-- | Like 'interpretH', but may be used to+-- interpret higher-order effects in terms of the final monad.+--+-- /Beware/: Any interpreters built using this (or 'Final' in general)+-- will _not_ respect local/global state semantics based on the order of+-- interpreters run. You should signal interpreters that make use of+-- 'Final' by adding a "-Final" suffix to the names of these.+--+-- State semantics of effects that are _not_+-- interpreted in terms of the final monad will always+-- appear local to effects that are interpreted in terms of the final monad.+--+-- State semantics between effects that are interpreted in terms of the final monad+-- depend on the final monad. I.e. if the final monad is a monad transformer stack,+-- then state semantics will depend on the order monad transformers are stacked.+interpretFinal+    :: forall e m r a+    .  (Member (Final m) r, Functor m)+    => (forall x n. e n x -> Strategic m n x)+    -> Sem (e ': r) a+    -> Sem r a+interpretFinal n =+  let+    go :: Sem (e ': r) x -> Sem r x+    go (Sem sem) = sem $ \u -> case decomp u of+      Right (Weaving e s wv ex ins) ->+        fmap ex $ withWeaving $ \s' wv' ins'+          -> fmap getCompose $+                runStrategy+                  (Compose (s <$ s'))+                  (fmap Compose . wv' . fmap (go . wv) . getCompose)+                  (ins' . getCompose >=> ins)+                  (n e)+      Left g -> liftSem (hoist go g)+    {-# INLINE go #-}+  in+    go+{-# INLINE interpretFinal #-}++------------------------------------------------------------------------------+-- | 'Strategic' is an environment in which you're capable of explicitly+-- threading higher-order effect states to the final monad.+-- This is based upon @Tactics@, (see 'Tactical'), and usage+-- is extremely similar.+type Strategic m n a = forall f. Functor f => Sem (WithStrategy m f n) (m (f a))++type WithStrategy m f n = WithTactics (Lift m) f n '[]++------------------------------------------------------------------------------+-- | Get a natural transformation capable of potentially inspecting values+-- inside of @f@. Binding the result of 'getInspectorS' produces a function that+-- can sometimes peek inside values returned by 'bindS'.+--+-- This is often useful for running callback functions that are not managed by+-- polysemy code.+--+-- See also 'getInspectorT'+getInspectorS :: Sem (WithStrategy m f n) (Inspector f)+getInspectorS = getInspectorT+{-# INLINE getInspectorS #-}++-- | Get the stateful environment of the world at the moment the+-- target monad is to be run.+-- Prefer 'pureS', 'runS' or 'bindS' instead of using this function+-- directly.+getInitialStateS :: Sem (WithStrategy m f n) (f ())+getInitialStateS = getInitialStateT+{-# INLINE getInitialStateS #-}++------------------------------------------------------------------------------+-- Lift a value into 'Strategic'.+pureS :: Applicative m => a -> Strategic m n a+pureS = fmap pure . pureT+{-# INLINE pureS #-}++------------------------------------------------------------------------------+-- Lifts an action of the final monad into 'Strategic'.+--+-- Note: you don't need to use this function if you already have a monadic+-- action with the functorial state woven into it, by the use of+-- 'runS' or 'bindS'.+-- In these cases, you need only use 'pure' to embed the action into the+-- 'Strategic' environment.+liftS :: Functor m => m a -> Strategic m n a+liftS m = do+  s <- getInitialStateS+  pure $ fmap (<$ s) m+{-# INLINE liftS #-}++------------------------------------------------------------------------------+-- | Lifts a monadic action into the stateful environment, in terms+-- of the final monad.+-- The stateful environment will be the same as the one that the target monad+-- is initially run in.+-- Use 'bindS'  if you'd prefer to explicitly manage your stateful environment.+runS :: Monad m => n a -> Sem (WithStrategy m f n) (m (f a))+runS = fmap runM . runT+{-# INLINE runS #-}++------------------------------------------------------------------------------+-- | Lift a kleisli action into the stateful environment, in terms of the final+-- monad. You can use 'bindS' to get an effect parameter of the form @a -> n b@+-- into something that can be used after calling 'runS' on an effect parameter+-- @n a@.+bindS :: Monad m => (a -> n b) -> Sem (WithStrategy m f n) (f a -> m (f b))+bindS = fmap (runM .) . bindT+{-# INLINE bindS #-}++------------------------------------------------------------------------------+-- | Internal function to process Strategies in terms of 'withWeaving'.+runStrategy :: Functor f+            => f ()+            -> (forall x. f (n x) -> m (f x))+            -> (forall x. f x -> Maybe x)+            -> Sem (WithStrategy m f n) a+            -> a+runStrategy s wv ins (Sem m) = runIdentity $ m $ \u -> case extract u of+  Weaving e s' _ ex' _ -> Identity $ ex' $ (<$ s') $ case e of+    GetInitialState -> s+    HoistInterpretation na -> sendM . wv . fmap na+    GetInspector -> Inspector ins++------------------------------------------------------------------------------+-- Lower a 'Sem' containing only a lifted, final monad into that monad.+-- The appearance of 'Lift' as the final effect+-- is to allow the use of operations that rely on a @'LastMember' ('Lift' m)@+-- constraint.+runFinal :: Monad m => Sem '[Final m, Lift m] a -> m a+runFinal = usingSem $ \u -> case decomp u of+  Right (Weaving (WithWeaving wav) s wv ex ins) ->+    ex <$> wav s (runFinal . wv) ins+  Left g -> case extract g of+    Weaving (Lift m) s _ ex _ -> ex . (<$ s) <$> m++------------------------------------------------------------------------------+-- Lower a 'Sem' containing two lifted monad into the final monad,+-- by interpreting one of the monads in terms of the other one.+--+-- This allows for the use of operations that rely on a @'LastMember' ('Lift' m)@+-- constraint, as long as @m@ can be transformed to the final monad;+-- but be warned, this breaks the implicit contract of @'LastMember' ('Lift' m)@+-- that @m@ _is_ the final monad, so depending on the final monad and operations+-- used, 'runFinalTrans' may become _unsafe_.+--+-- For example, 'runFinalTrans' is unsafe with 'runAsync' if+-- the final monad is non-deterministic, or a continuation+-- monad.+runFinalLift :: Monad m+              => (forall x. n x -> m x)+              -> Sem [Final m, Lift m, Lift n] a+              -> m a+runFinalLift nat = usingSem $ \u -> case decomp u of+  Right (Weaving (WithWeaving wav) s wv ex ins) ->+    ex <$> wav s (runFinalLift nat . wv) ins+  Left g -> case decomp g of+    Right (Weaving (Lift m) s _ ex _) -> ex . (<$ s) <$> m+    Left g' -> case extract g' of+      Weaving (Lift n) s _ ex _ -> ex . (<$ s) <$> nat n++------------------------------------------------------------------------------+-- | 'runFinalTrans', specialized to transform 'IO' to a 'MonadIO'.+runFinalLiftIO :: MonadIO m+               => Sem [Final m, Lift m, Lift IO] a+               -> m a+runFinalLiftIO = runFinalLift liftIO
+ src/Polysemy/Final/Async.hs view
@@ -0,0 +1,38 @@+module Polysemy.Final.Async+  (+    module Polysemy.Async+  , module Polysemy.Final+  , runAsyncFinal+  ) where++import qualified Control.Concurrent.Async as A++import Polysemy+import Polysemy.Async+import Polysemy.Final++------------------------------------------------------------------------------+-- | Run an 'Async' effect through final 'IO'+--+-- This can be used as an alternative to 'runAsyncInIO'.+--+-- /Beware/: Effects that aren't interpreted in terms of 'IO'+-- will have local state semantics in regards to 'Async' effects+-- interpreted this way. See 'interpretFinal'.+--+-- Notably, unlike 'runAsync', this is not consistent with+-- 'Polysemy.State.State' unless 'Polysemy.State.runStateInIORef' is used.+-- State that seems like it should be threaded globally throughout the `Async`+-- /will not be./+--+-- Prefer 'runAsync' unless its unsafe or inefficient in the context of your+-- application.+runAsyncFinal :: Member (Final IO) r+              => Sem (Async ': r) a+              -> Sem r a+runAsyncFinal = interpretFinal $ \case+  Async m -> do+    ins <- getInspectorS+    m' <- runS m+    liftS $ A.async (inspect ins <$> m')+  Await a -> liftS (A.wait a)
+ src/Polysemy/Final/Error.hs view
@@ -0,0 +1,61 @@+module Polysemy.Final.Error+  (+    module Polysemy.Error+  , module Polysemy.Final+  , runErrorInIOFinal+  ) where++import           Control.Exception hiding (throw, catch)+import qualified Control.Exception as X+import           Data.Typeable (Typeable, typeRep)+import           Polysemy+import           Polysemy.Final+import           Polysemy.Error++------------------------------------------------------------------------------+-- | Run an 'Error' effect as an 'IO' 'Exception'.+--+-- This can be used as an alternative to 'runErrorInIO'+--+-- /Beware/: Effects that aren't interpreted in terms of 'IO'+-- will have local state semantics in regards to 'Error' effects+-- interpreted this way. See 'interpretFinal'.+runErrorInIOFinal+    :: ( Typeable e+       , Member (Final IO) r+       )+    => Sem (Error e ': r) a+    -> Sem r (Either e a)+runErrorInIOFinal sem = withStrategic $ do+  m' <- runS (runErrorAsExcFinal sem)+  s <- getInitialStateS+  pure $+    either+      ((<$ s) . Left . unwrapExc)+      (fmap Right)+    <$> try m'++runErrorAsExcFinal+    :: forall e r a+    .  ( Typeable e+       , Member (Final IO) r+       )+    => Sem (Error e ': r) a+    -> Sem r a+runErrorAsExcFinal = interpretFinal $ \case+  Throw e   -> pure $ throwIO $ WrappedExc e+  Catch m h -> do+    m' <- runS m+    h' <- bindS h+    s  <- getInitialStateS+    pure $ X.catch m' $ \(se :: WrappedExc e) ->+      h' (unwrapExc se <$ s)+++newtype WrappedExc e = WrappedExc { unwrapExc :: e }+  deriving (Typeable)++instance Typeable e => Show (WrappedExc e) where+  show = mappend "WrappedExc: " . show . typeRep++instance (Typeable e) => Exception (WrappedExc e)
+ src/Polysemy/Final/Fixpoint.hs view
@@ -0,0 +1,22 @@+module Polysemy.Final.Fixpoint+  (+    module Polysemy.Fixpoint+  , module Polysemy.Final+  , runFixpointFinal+  ) where++import Polysemy+import Polysemy.Final+import Polysemy.Fixpoint++import Control.Monad.Fix++-----------------------------------------------------------------------------+-- | Run a 'Fixpoint' effect through a final 'MonadFix'+runFixpointFinal :: (Member (Final m) r, MonadFix m)+                 => Sem (Fixpoint ': r) a+                 -> Sem r a+runFixpointFinal = interpretFinal $ \case+  Fixpoint f -> do+    f' <- bindS f+    pure $ mfix f'
+ src/Polysemy/Final/MTL.hs view
@@ -0,0 +1,94 @@+module Polysemy.Final.MTL+  (+    module Polysemy.Final+  , runErrorFinal+  , runReaderFinal+  , runStateFinal+  , runWriterFinal+  ) where++import Control.Monad.Error.Class hiding (Error)+import Control.Monad.Reader.Class+import Control.Monad.State.Class+import Control.Monad.Writer.Class++import Polysemy+import Polysemy.Final+import Polysemy.Error hiding (throw, catch)+import Polysemy.Reader hiding (ask, local)+import Polysemy.State hiding (get, put)+import Polysemy.Writer hiding (tell, listen, pass)++-----------------------------------------------------------------------------+-- | Run an 'Error' effect through a final 'MonadError'+--+-- /Beware/: Effects that aren't interpreted in terms of the final+-- monad will have local state semantics in regards to 'Error' effects+-- interpreted this way. See 'interpretFinal'.+runErrorFinal :: (Member (Final m) r, MonadError e m)+              => Sem (Error e ': r) a+              -> Sem r a+runErrorFinal = interpretFinal $ \case+  Throw e   -> pure $ throwError e+  Catch m h -> do+    m' <- runS m+    h' <- bindS h+    s <- getInitialStateS+    pure $ m' `catchError` (h' . (<$ s))+++-----------------------------------------------------------------------------+-- | Run a 'Reader' effect through a final 'MonadReader'+--+-- /Beware/: Effects that aren't interpreted in terms of the final+-- monad will have local state semantics in regards to 'Reader' effects+-- interpreted this way. See 'interpretFinal'.+runReaderFinal :: (Member (Final m) r, MonadReader i m)+               => Sem (Reader i ': r) a+               -> Sem r a+runReaderFinal = interpretFinal $ \case+  Ask       -> liftS ask+  Local f m -> do+    m' <- runS m+    pure $ local f m'++-----------------------------------------------------------------------------+-- | Run a 'State' effect through a 'MonadState'+--+-- Despite the name, the target monad need not actually be the final+-- monad. The "-Final" suffix reflects that this interpreter+-- has the unusual semantics of interpreters that runs+-- effects by embedding them into another monad.+--+-- /Beware/: Effects that aren't interpreted in terms of the final+-- monad will have local state semantics in regards to 'State' effects+-- interpreted this way. See 'interpretFinal'.+runStateFinal :: (Member (Lift m) r, MonadState s m)+               => Sem (State s ': r) a+               -> Sem r a+runStateFinal = interpret $ \case+  Get   -> sendM get+  Put s -> sendM (put s)++-----------------------------------------------------------------------------+-- | Run a 'Writer' effect through a final 'MonadWriter'+--+-- /Beware/: Effects that aren't interpreted in terms of the final+-- monad will have local state semantics in regards to 'Writer' effects+-- interpreted this way. See 'interpretFinal'.+runWriterFinal :: (Member (Final m) r, MonadWriter o m)+               => Sem (Writer o ': r) a+               -> Sem r a+runWriterFinal = interpretFinal $ \case+  Tell s    -> liftS (tell s)+  Listen m -> do+    m' <- runS m+    pure $+      (\ ~(s, o) -> (,) o <$> s) <$> listen m'+  Pass m -> do+    m'  <- runS m+    ins <- getInspectorS+    pure $ pass $ do+      t <- m'+      let f = maybe id fst (inspect ins t)+      pure (fmap snd t, f)
+ src/Polysemy/Final/Resource.hs view
@@ -0,0 +1,45 @@+module Polysemy.Final.Resource+  (+    module Polysemy.Resource+  , module Polysemy.Final+  , runResourceFinal+  ) where++import qualified Control.Exception as X+import           Polysemy+import           Polysemy.Resource+import           Polysemy.Final+++------------------------------------------------------------------------------+-- | Run a 'Resource' effect in terms of 'X.bracket' through final 'IO'+--+-- This can be used as an alternative to 'runResourceInIO'+--+-- /Beware/: Effects that aren't interpreted in terms of 'IO'+-- will have local state semantics in regards to 'Resource' effects+-- interpreted this way. See 'interpretFinal'.+--+-- Notably, unlike 'runResourceBase', this is not consistent with+-- 'Polysemy.State.State' unless 'Polysemy.State.runStateInIORef' is used.+-- State that seems like it should be threaded globally throughout 'bracket's+-- /will not be./+--+-- Prefer 'runResourceBase' unless its unsafe or inefficient in the context of+-- your application.+runResourceFinal :: Member (Final IO) r+                 => Sem (Resource ': r) a+                 -> Sem r a+runResourceFinal = interpretFinal $ \case+  Bracket alloc dealloc use -> do+    a <- runS  alloc+    d <- bindS dealloc+    u <- bindS use+    pure $ X.bracket a d u++  BracketOnError alloc dealloc use -> do+    a <- runS  alloc+    d <- bindS dealloc+    u <- bindS use+    pure $ X.bracketOnError a d u+{-# INLINE runResourceFinal #-}
+ src/Polysemy/Floodgate.hs view
@@ -0,0 +1,64 @@+{-# LANGUAGE TemplateHaskell #-}++module Polysemy.Floodgate+  ( -- * Effect+    Floodgate (..)+    -- * Actions+  , hold+  , release++    -- * Interpretations+  , runFloodgate+  , runFloodgateDry+  ) where++import Control.Monad+import GHC.Types+import Polysemy+import Polysemy.State+import Unsafe.Coerce++------------------------------------------------------------------------------+-- |+--+-- @since 0.3.1.0+data Floodgate m a where+  Hold    :: m () -> Floodgate m ()+  Release :: Floodgate m ()++makeSem ''Floodgate+++------------------------------------------------------------------------------+-- |+--+-- @since 0.3.1.0+runFloodgate+    :: Sem (Floodgate ': r) a+    -> Sem r a+runFloodgate = fmap snd . runState @[Any] [] . reinterpretH+  ( \case+      Hold m -> do+        m' <- fmap void $ runT m+        -- These 'Any's are here because the monadic action references 'r', and+        -- if we exposed that, 'r' would be an infinite type+        modify (unsafeCoerce @_ @Any (raise $ runFloodgate m') :)+        getInitialStateT++      Release -> do+        ms' <- gets (fmap unsafeCoerce . reverse)+        sequence_ ms'+        getInitialStateT+  )+++------------------------------------------------------------------------------+-- | Like 'runFloodgate', but will do a final flush to 'release' anything that+-- might still be behind the floodgate.+--+-- @since 0.3.1.0+runFloodgateDry+    :: Sem (Floodgate ': r) a+    -> Sem r a+runFloodgateDry m = runFloodgate $ m <* release+
src/Polysemy/KVStore.hs view
@@ -6,17 +6,28 @@      -- * Actions   , lookupKV+  , lookupOrThrowKV+  , existsKV   , writeKV   , deleteKV   , updateKV+  , modifyKV      -- * Interpretations   , runKVStoreAsState   , runKVStorePurely+  , runKVStoreInRedis   ) where +import           Control.Monad+import           Data.Binary (Binary)+import           Data.ByteString (ByteString) import qualified Data.Map as M+import           Data.Maybe (isJust)+import qualified Database.Redis as R import           Polysemy+import           Polysemy.Error+import           Polysemy.Redis.Utils import           Polysemy.State  @@ -40,6 +51,45 @@ {-# INLINE deleteKV #-}  +------------------------------------------------------------------------------+-- |+--+-- @since 0.3.1.0+lookupOrThrowKV+    :: Members '[ KVStore k v+                , Error e+                ] r+    => (k -> e)+    -> k+    -> Sem r v+lookupOrThrowKV f k =+  fromEither . maybe (Left $ f k) Right =<< lookupKV k+++------------------------------------------------------------------------------+-- |+--+-- @since 0.3.1.0+existsKV :: Member (KVStore k v) r => k -> Sem r Bool+existsKV = fmap isJust . lookupKV+++------------------------------------------------------------------------------+-- |+--+-- @since 0.3.1.0+modifyKV+    :: Member (KVStore k v) r+    => v  -- ^ Default value if the key isn't present+    -> (v -> v)+    -> k+    -> Sem r ()+modifyKV d f k =+  lookupKV k >>= \case+    Just v  -> writeKV k $ f v+    Nothing -> writeKV k $ f d++ runKVStoreAsState :: Ord k => Sem (KVStore k v ': r) a -> Sem (State (M.Map k v) ': r) a runKVStoreAsState = reinterpret $ \case   LookupKV k   -> gets $ M.lookup k@@ -54,4 +104,31 @@     -> Sem r (M.Map k v, a) runKVStorePurely m = runState m . runKVStoreAsState {-# INLINE runKVStorePurely #-}+++runKVStoreInRedis+    :: ( Member (Lift R.Redis) r+       , Member (Error R.Reply) r+       , Binary k+       , Binary v+       )+    => (k -> ByteString)+    -> Sem (KVStore k v ': r) a+    -> Sem r a+runKVStoreInRedis pf = interpret $ \case+  LookupKV k -> do+    res <- fromEitherM $ R.hget (pf k) $ putForRedis k+    pure $ fmap getFromRedis res++  UpdateKV k Nothing ->+    void . fromEitherM+         . R.hdel (pf k)+         . pure+         $ putForRedis k++  UpdateKV k (Just v) ->+    void . fromEitherM+         . R.hset (pf k) (putForRedis k)+         $ putForRedis v+{-# INLINE runKVStoreInRedis #-} 
src/Polysemy/Random.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE BlockArguments  #-} {-# LANGUAGE TemplateHaskell #-}  module Polysemy.Random@@ -35,7 +34,7 @@     => q     -> Sem (Random ': r) a     -> Sem r (q, a)-runRandom q = runState q . reinterpret \case+runRandom q = runState q . reinterpret (\case   Random -> do     ~(a, q') <- gets @q R.random     put q'@@ -44,6 +43,7 @@     ~(a, q') <- gets @q $ R.randomR r     put q'     pure a+                                       ) {-# INLINE runRandom #-}  
+ src/Polysemy/Redis/Utils.hs view
@@ -0,0 +1,24 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++module Polysemy.Redis.Utils where++import           Data.Binary (Binary)+import qualified Data.Binary as B+import           Data.Binary.Get (runGet)+import           Data.Binary.Put (runPut)+import           Data.ByteString (ByteString)+import qualified Data.ByteString.Lazy.Char8 as L+import           GHC.Exts+++newtype Path = Path { getPath :: ByteString }+  deriving (Eq, Ord, Show, IsString)+++putForRedis :: Binary a => a -> ByteString+putForRedis = L.toStrict . runPut . B.put+++getFromRedis :: Binary a => ByteString -> a+getFromRedis = runGet B.get . L.fromStrict+
+ src/Polysemy/SetStore.hs view
@@ -0,0 +1,67 @@+{-# LANGUAGE TemplateHaskell #-}++module Polysemy.SetStore where++import           Control.Monad+import           Data.Binary (Binary)+import           Data.Foldable+import qualified Data.Set as S+import qualified Database.Redis as R+import           Polysemy+import           Polysemy.Alias+import           Polysemy.Error+import           Polysemy.KVStore+import           Polysemy.Redis.Utils++++data SetStore k v m a where+  AddS :: k -> v -> SetStore k v m ()+  DelS :: k -> v -> SetStore k v m ()+  MemberS :: k -> v -> SetStore k v m Bool++makeSem ''SetStore+++runSetStoreAsKVStore+    :: ( Member (KVStore k (S.Set v)) r+       , Ord v+       )+    => InterpreterOf (SetStore k v) r+runSetStoreAsKVStore = interpret $ \case+  AddS k v ->+    lookupKV k >>= \case+      Just s  -> writeKV k $ S.insert v s+      Nothing -> writeKV k $ S.singleton v+  DelS k v -> do+    ms <- lookupKV k+    for_ ms $ writeKV k . S.delete v+  MemberS k v ->+    pure . maybe False (S.member v) =<< lookupKV k+{-# INLINE runSetStoreAsKVStore #-}+++runSetStoreInRedis+    :: ( Member (Lift R.Redis) r+       , Member (Error R.Reply) r+       , Binary k+       , Binary v+       )+    => (k -> Path)+    -> InterpreterOf (SetStore k v) r+runSetStoreInRedis pf = interpret $ \case+  AddS k v -> void+            . fromEitherM+            . R.sadd (getPath $ pf k)+            . pure+            $ putForRedis v+  DelS k v -> void+            . fromEitherM+            . R.srem (getPath $ pf k)+            . pure+            $ putForRedis v+  MemberS k v -> fromEitherM+               . R.sismember (getPath $ pf k)+               $ putForRedis v+{-# INLINE runSetStoreInRedis #-}+
src/Polysemy/Several.hs view
@@ -1,14 +1,12 @@-{-# LANGUAGE BlockArguments  #-} {-# LANGUAGE TemplateHaskell #-}  module Polysemy.Several-        ( -- * Data-          HList(..)-        , TypeMap-        , TypeConcat-        , runSeveral-        )-where+  ( -- * Data+    HList(..)+  , TypeMap+  , TypeConcat+  , runSeveral+  ) where  import Polysemy import Data.Kind@@ -24,7 +22,6 @@     HNil  :: HList '[]     (:::) :: a -> HList (b :: [Type]) -> HList (a ': b) -type Eff = (Type -> Type) -> Type -> Type ------------------------------------------------------------------------------ -- | A map function over type level lists. For example, the following two -- lines are equivalent:@@ -65,9 +62,10 @@ -- > runStates :: HList t -> Sem (TypeConcat (TypeMap State t) r) a -> Sem r a -- > runStates = runSeveral (fmap (fmap snd) . runState) runSeveral-    :: (forall r k x. k -> Sem (e k ': r) x -> Sem r x)+    :: (forall r' k x. k -> Sem (e k ': r') x -> Sem r' x)     -> HList t     -> Sem (TypeConcat (TypeMap e t) r) a     -> Sem r a runSeveral f (a ::: as) = runSeveral f as . f a runSeveral _ HNil       = id+
test/ConstraintAbsorberSpec.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE OverloadedStrings   #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications    #-} @@ -15,7 +14,6 @@ import           Polysemy.ConstraintAbsorber.MonadWriter import           Polysemy.ConstraintAbsorber.MonadError -import qualified Data.Text                     as T import           Test.Hspec import           Control.Monad                 as M @@ -30,8 +28,8 @@ that these come from external libraries so you can't so easily re-write them. -}-getEnvLength :: S.MonadReader T.Text m => m Int-getEnvLength = S.ask >>= return . T.length+getEnvLength :: S.MonadReader String m => m Int+getEnvLength = S.ask >>= return . length  replicateTell :: S.MonadWriter [Int] m => Int -> Int -> m () replicateTell n m = M.replicateM_ n $ S.tell [m]@@ -43,14 +41,14 @@   return m  -- this one is exceptionally boring-throwOnZero :: S.MonadError T.Text m => Int -> m Int+throwOnZero :: S.MonadError String m => Int -> m Int throwOnZero n = do   M.when (n == 0) $ S.throwError "Zero!"   return n  someOfAll-  :: (S.MonadReader T.Text m, S.MonadWriter [Int] m, S.MonadState Int m)-  => m T.Text+  :: (S.MonadReader String m, S.MonadWriter [Int] m, S.MonadState Int m)+  => m String someOfAll = do   n <- S.get   S.tell [n]@@ -99,9 +97,9 @@     flip shouldBe (Left "Zero!") . run . runError $ absorbError $ throwOnZero 0    let runRWS-        :: T.Text+        :: String         -> Int-        -> Sem '[Reader T.Text, State Int, Writer [Int]] a+        -> Sem '[Reader String, State Int, Writer [Int]] a         -> ([Int], (Int, a))       runRWS env0 s0 = run . runWriter . runState s0 . runReader env0 
+ test/FinalSpec.hs view
@@ -0,0 +1,164 @@+{-# LANGUAGE RecursiveDo #-}+module FinalSpec where++import Test.Hspec++import Control.Monad.State  hiding (MonadState(..), modify)+import Control.Monad.Except hiding (MonadError(..))+import Control.Monad.Writer hiding (MonadWriter(..), censor)++import Data.Either+import Data.IORef++import Polysemy++import Polysemy.Trace+import Polysemy.State++import Polysemy.Final.Async+import Polysemy.Final.Fixpoint+import Polysemy.Final.Error++import Polysemy.Reader+import Polysemy.Writer+import Polysemy.Final.MTL++data Node a = Node a (IORef (Node a))++mkNode :: (Member (Lift IO) r, Member Fixpoint r)+       => a+       -> Sem r (Node a)+mkNode a = mdo+  let nd = Node a p+  p <- sendM $ newIORef nd+  return nd++linkNode :: Member (Lift IO) r+         => Node a+         -> Node a+         -> Sem r ()+linkNode (Node _ r) b =+  sendM $ writeIORef r b++readNode :: Node a -> a+readNode (Node a _) = a++follow :: Member (Lift IO) r+       => Node a+       -> Sem r (Node a)+follow (Node _ ref) = sendM $ readIORef ref++test1 :: IO (Either Int (String, Int, Maybe Int))+test1 = do+  ref <- newIORef "abra"+  runFinal+    . runStateInIORef ref -- Order of these interpreters don't matter+    . runErrorInIOFinal+    . runFixpointFinal+    . runAsyncFinal+     $ do+     n1 <- mkNode 1+     n2 <- mkNode 2+     linkNode n2 n1+     aw <- async $ do+       linkNode n1 n2+       modify (++"hadabra")+       n2' <- follow n2+       throw (readNode n2')+     m <- await aw `catch` (\s -> return $ Just s)+     n1' <- follow n1+     s <- get+     return (s, readNode n1', m)++test2 :: IO ([String], Either () ())+test2 =+    runFinal+  . runTraceAsList+  . runErrorInIOFinal+  . runAsyncFinal+  $ do+  fut <- async $ do+    trace "Global state semantics?"+  catch (trace "What's that?" *> throw ()) (\_ -> return ())+  _ <- await fut+  trace "Nothing at all."++test3 :: Int -> (Either Bool (([String], Int), Int), [Int])+test3 i =+  let+    g = do+      j  <- ask+      j' <- get+      tell [j, j']+      put (j' + 7)+      trace "message"+      when (j' == 1) $ throw True+      when (j' == 2) $ throw False+      return j+  in+    ($ i)+  . runWriterT+  . runExceptT+  . (`runStateT` 0)+  . runFinal+  . runTraceAsList -- Order of these interpreters don't matter+  . runWriterFinal+  . runStateFinal+  . runErrorFinal+  . runReaderFinal+  $ do+    ask >>= put+    res <-+      censor (++[777]) (local (+1) g)+      `catch`+      (\e -> trace "not" *> if e then throw e else return (-1))+    trace "received"+    j' <- get+    tell [j']+    return res++spec :: Spec+spec = do+  describe "Final on IO" $ do+    it "should terminate successfully, with no exceptions,\+        \ and have global state semantics on State." $ do+      res1 <- test1+      res1 `shouldSatisfy` isRight+      case res1 of+        Right (s, i, j) -> do+          i `shouldBe` 2+          j `shouldBe` Just 1+          s `shouldBe` "abrahadabra"+        _ -> pure ()++    it "should treat trace with local state semantics" $ do+      res2 <- test2+      res2 `shouldBe` (["Nothing at all."], Right ())++  describe "Final with MTL" $ do+    it "should all work without issue" $ do+      let (r, written) = test3 0+      written `shouldBe` [1,0,777,7]+      r `shouldSatisfy` isRight+      case r of+        Right ((lg, ret), st) -> do+          lg `shouldBe` ["message", "received"]+          ret `shouldBe` 1+          st `shouldBe` 7+        _ -> pure ()++    it "should fail, dropping trace, state, and censoring" $ do+      let (r, written) = test3 1+      r `shouldBe` Left True+      written `shouldBe` [2, 1]++    it "should catch exception, locally dropping trace and state, and not censor" $ do+      let (r, written) = test3 2+      written `shouldBe` [3,2,2]+      r `shouldSatisfy` isRight+      case r of+        Right ((lg, ret), st) -> do+          lg `shouldBe` ["not", "received"]+          ret `shouldBe` (-1)+          st `shouldBe` 2+        _ -> pure ()
+ test/FloodgateSpec.hs view
@@ -0,0 +1,31 @@+module FloodgateSpec where++import Test.Hspec+import Polysemy+import Polysemy.Floodgate+import Polysemy.Trace++spec :: Spec+spec = describe "Floodgate" $ do+  it "should delay held traces until release" $ do+    let (ts, n) = run . runTraceAsList . runFloodgate $ do+          hold $ trace "first1"+          hold $ trace "first2"+          trace "not held"+          hold $ trace "second"+          trace "not held again"+          hold $ trace "third"+          release+          trace "finished"+          pure $ id @Int 17++    n `shouldBe` 17+    ts `shouldBe`+      [ "not held"+      , "not held again"+      , "first1"+      , "first2"+      , "second"+      , "third"+      , "finished"+      ]
test/SeveralSpec.hs view
@@ -47,12 +47,16 @@   pure $ (a, b, c)  +runReaders :: HList t -> Sem (TypeConcat (TypeMap Reader t) r) a -> Sem r a runReaders = runSeveral runReader +runStates :: HList t -> Sem (TypeConcat (TypeMap State t) r) a -> Sem r a runStates = runSeveral (fmap (fmap snd) . runState) +runConstInputs :: HList t -> Sem (TypeConcat (TypeMap Input t) r) a -> Sem r a runConstInputs = runSeveral runConstInput +spec :: Spec spec = do   describe "runReaders" $ do     let original = runReader 5 . runReader "test" . runReader True $ readerProgram@@ -76,3 +80,4 @@      it "should be equivalent to composed runConstInput" $ do       run original `shouldBe` run new+