polysemy 0.3.0.1 → 0.4.0.0
raw patch · 7 files changed
+230/−18 lines, 7 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ Polysemy.Resource: [BracketOnError] :: m a -> (a -> m c) -> (a -> m b) -> Resource m b
+ Polysemy.Resource: bracketOnError :: forall r_ar0A a_ar0r c_ar0s a_aqYj. Member Resource r_ar0A => Sem r_ar0A a_ar0r -> (a_ar0r -> Sem r_ar0A c_ar0s) -> (a_ar0r -> Sem r_ar0A a_aqYj) -> Sem r_ar0A a_aqYj
+ Polysemy.Resource: finally :: Member Resource r => Sem r a -> Sem r b -> Sem r a
+ Polysemy.Resource: onException :: Member Resource r => Sem r a -> Sem r b -> Sem r a
+ Polysemy.Resource: runResourceInIO :: forall r a. Member (Lift IO) r => (forall x. Sem r x -> IO x) -> Sem (Resource : r) a -> Sem r a
- Polysemy.Input: input :: forall a_at25 r_at2Z. Member (Input a_at25) r_at2Z => Sem r_at2Z a_at25
+ Polysemy.Input: input :: forall a_atM9 r_atN3. Member (Input a_atM9) r_atN3 => Sem r_atN3 a_atM9
- Polysemy.Output: output :: forall o_asoD r_aspw. Member (Output o_asoD) r_aspw => o_asoD -> Sem r_aspw ()
+ Polysemy.Output: output :: forall o_at8H r_at9A. Member (Output o_at8H) r_at9A => o_at8H -> Sem r_at9A ()
- Polysemy.Reader: ask :: forall a_atmS r_atoq. Member (Reader a_atmS) r_atoq => Sem r_atoq a_atmS
+ Polysemy.Reader: ask :: forall a_au6W r_au8u. Member (Reader a_au6W) r_au8u => Sem r_au8u a_au6W
- Polysemy.Reader: local :: forall i_atmQ r_ator a_atmS. Member (Reader i_atmQ) r_ator => (i_atmQ -> i_atmQ) -> Sem r_ator a_atmS -> Sem r_ator a_atmS
+ Polysemy.Reader: local :: forall i_au6U r_au8v a_au6W. Member (Reader i_au6U) r_au8v => (i_au6U -> i_au6U) -> Sem r_au8v a_au6W -> Sem r_au8v a_au6W
- Polysemy.Resource: [Bracket] :: m a -> (a -> m ()) -> (a -> m b) -> Resource m b
+ Polysemy.Resource: [Bracket] :: m a -> (a -> m c) -> (a -> m b) -> Resource m b
- Polysemy.Resource: bracket :: forall r_aqZq a_aqZl a_aqYi. Member Resource r_aqZq => Sem r_aqZq a_aqZl -> (a_aqZl -> Sem r_aqZq ()) -> (a_aqZl -> Sem r_aqZq a_aqYi) -> Sem r_aqZq a_aqYi
+ Polysemy.Resource: bracket :: forall r_ar0z a_ar0j c_ar0k a_aqYj. Member Resource r_ar0z => Sem r_ar0z a_ar0j -> (a_ar0j -> Sem r_ar0z c_ar0k) -> (a_ar0j -> Sem r_ar0z a_aqYj) -> Sem r_ar0z a_aqYj
- Polysemy.Resource: runResource :: forall r a. Member (Lift IO) r => (forall x. Sem r x -> IO x) -> Sem (Resource : r) a -> Sem r a
+ Polysemy.Resource: runResource :: forall r a. Sem (Resource : r) a -> Sem r a
- Polysemy.State: get :: forall a_arjD r_arl7. Member (State a_arjD) r_arl7 => Sem r_arl7 a_arjD
+ Polysemy.State: get :: forall a_as3H r_as5b. Member (State a_as3H) r_as5b => Sem r_as5b a_as3H
- Polysemy.State: put :: forall s_arjB r_arl8. Member (State s_arjB) r_arl8 => s_arjB -> Sem r_arl8 ()
+ Polysemy.State: put :: forall s_as3F r_as5c. Member (State s_as3F) r_as5c => s_as3F -> Sem r_as5c ()
- Polysemy.Trace: trace :: forall r_atJd. Member Trace r_atJd => String -> Sem r_atJd ()
+ Polysemy.Trace: trace :: forall r_auth. Member Trace r_auth => String -> Sem r_auth ()
- Polysemy.Writer: censor :: forall o_au8Q r_aubi a_au8S. Member (Writer o_au8Q) r_aubi => (o_au8Q -> o_au8Q) -> Sem r_aubi a_au8S -> Sem r_aubi a_au8S
+ Polysemy.Writer: censor :: forall o_auSU r_auVm a_auSW. Member (Writer o_auSU) r_auVm => (o_auSU -> o_auSU) -> Sem r_auVm a_auSW -> Sem r_auVm a_auSW
- Polysemy.Writer: listen :: forall o_au8Q r_aubh a_auaW. Member (Writer o_au8Q) r_aubh => Sem r_aubh a_auaW -> Sem r_aubh (o_au8Q, a_auaW)
+ Polysemy.Writer: listen :: forall o_auSU r_auVl a_auV0. Member (Writer o_auSU) r_auVl => Sem r_auVl a_auV0 -> Sem r_auVl (o_auSU, a_auV0)
- Polysemy.Writer: tell :: forall o_au8Q r_aubg. Member (Writer o_au8Q) r_aubg => o_au8Q -> Sem r_aubg ()
+ Polysemy.Writer: tell :: forall o_auSU r_auVk. Member (Writer o_auSU) r_auVk => o_auSU -> Sem r_auVk ()
Files
- ChangeLog.md +23/−1
- README.md +3/−5
- bench/Poly.hs +1/−1
- polysemy.cabal +3/−2
- src/Polysemy/Internal.hs +2/−2
- src/Polysemy/Resource.hs +116/−7
- test/BracketSpec.hs +82/−0
ChangeLog.md view
@@ -1,13 +1,30 @@ # Changelog for polysemy +## 0.4.0.0 (2019-06-12)++### Breaking Changes++- Renamed `runResource` to `runResourceInIO`++### Other Changes++- Added `runResource`, which runs a `Resource` purely+- Added `onException`, `finally` and `bracketOnError` to `Resource`+- Added a new function, `runResource` which performs bracketing for pure code+ ## 0.3.0.1 (2019-06-09) - Fixed a type error in the benchmark caused by deprecation of `Semantic` ## 0.3.0.0 (2019-06-01) +### Breaking Changes+ - Removed all deprecated names - Moved `Random` effect to `polysemy-zoo`++### Other Changes+ - `makeSem` can now be used to create term-level operators (thanks to @TheMatten) @@ -27,9 +44,14 @@ ## 0.2.0.0 (2019-05-23) +### Breaking Changes++- Lower precedence of `.@` and `.@@` to 8, from 9++### Other Changes+ - Fixed a serious bug in `interpretH` and friends, where higher-order effects would always be run with the current interpreter.-- Lower precedence of `.@` and `.@@` to 8, from 9 - Users need no longer require `inlineRecursiveCalls` --- the `polysemy-plugin-0.2.0.0` will do it automatically when compiling with `-O` - Deprecated `inlineRecursiveCalls`; slated for removal in the next version
README.md view
@@ -157,7 +157,7 @@ _ -> writeTTY input >> writeTTY "no exceptions" main :: IO (Either CustomException ())-main = (runM .@ runResource .@@ runErrorInIO @CustomException) . runTeletypeIO $ program+main = (runM .@ runResourceInIO .@@ runErrorInIO @CustomException) . runTeletypeIO $ program ``` Easy.@@ -175,11 +175,9 @@ ```haskell runResource :: forall r a- . Member (Lift IO) r- => (∀ x. Sem r x -> IO x)- -> Sem (Resource ': r) a+ . Sem (Resource ': r) a -> Sem r a-runResource finish = interpret $ \case+runResource = interpret $ \case ... ```
bench/Poly.hs view
@@ -43,7 +43,7 @@ zoinks :: IO (Either Bool Bool) zoinks = fmap (fmap snd)- . (runM .@ runResource .@@ runErrorInIO)+ . (runM .@ runResourceInIO .@@ runErrorInIO) . runState False $ prog
polysemy.cabal view
@@ -4,10 +4,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: 377e703e0e8b13978cd5936eac0b54f8d9ddba81bf1bdedc130d48bb4f7133dd+-- hash: 4be5160aee3aafedfc1de37204f6dbea8e61f8aceaee0deaa59520e662451bd3 name: polysemy-version: 0.3.0.1+version: 0.4.0.0 synopsis: Higher-order, low-boilerplate, zero-cost free monads. description: Please see the README on GitHub at <https://github.com/isovector/polysemy#readme> category: Language@@ -88,6 +88,7 @@ main-is: Main.hs other-modules: AlternativeSpec+ BracketSpec FusionSpec HigherOrderSpec InspectorSpec
src/Polysemy/Internal.hs view
@@ -357,10 +357,10 @@ ------------------------------------------------------------------------------ -- | Some interpreters need to be able to lower down to the base monad (often -- 'IO') in order to function properly --- some good examples of this are--- 'Polysemy.Error.runErrorInIO' and 'Polysemy.Resource.runResource'.+-- 'Polysemy.Error.runErrorInIO' and 'Polysemy.Resource.runResourceInIO'. -- -- However, these interpreters don't compose particularly nicely; for example,--- to run 'Polysemy.Resource.runResource', you must write:+-- to run 'Polysemy.Resource.runResourceInIO', you must write: -- -- @ -- runM . runErrorInIO runM
src/Polysemy/Resource.hs view
@@ -6,9 +6,13 @@ -- * Actions , bracket+ , bracketOnError+ , finally+ , onException -- * Interpretations , runResource+ , runResourceInIO ) where import qualified Control.Exception as X@@ -23,34 +27,139 @@ Bracket :: m a -- ^ Action to allocate a resource.- -> (a -> m ())+ -> (a -> m c) -- ^ Action to cleanup the resource. This is guaranteed to be -- called. -> (a -> m b) -- ^ Action which uses the resource. -> Resource m b+ BracketOnError+ :: m a+ -- ^ Action to allocate a resource.+ -> (a -> m c)+ -- ^ Action to cleanup the resource. This will only be called if the+ -- "use" block fails.+ -> (a -> m b)+ -- ^ Action which uses the resource.+ -> Resource m b makeSem ''Resource ------------------------------------------------------------------------------+-- | Like 'bracket', but for the simple case of one computation to run+-- afterward.+--+-- @since 0.4.0.0+finally+ :: Member Resource r+ => Sem r a -- ^ computation to run first+ -> Sem r b -- ^ computation to run afterward (even if an exception was raised)+ -> Sem r a+finally act end = bracket (pure ()) (pure end) (const act)+++------------------------------------------------------------------------------+-- | Like 'bracketOnError', but for the simple case of one computation to run+-- afterward.+--+-- @since 0.4.0.0+onException+ :: Member Resource r+ => Sem r a -- ^ computation to run first+ -> Sem r b -- ^ computation to run afterward if an exception was raised+ -> Sem r a+onException act end = bracketOnError (pure ()) (const end) (const act)+++------------------------------------------------------------------------------ -- | Run a 'Resource' effect via in terms of 'X.bracket'.-runResource- :: forall r a+--+-- __Note:__ This function used to be called @runResource@ prior to 0.4.0.0.+--+-- @since 0.4.0.0+runResourceInIO+ :: ∀ r a . Member (Lift IO) r => (∀ x. Sem r x -> IO x) -- ^ Strategy for lowering a 'Sem' action down to 'IO'. This is likely -- some combination of 'runM' and other interpreters composed via '.@'. -> Sem (Resource ': r) a -> Sem r a-runResource finish = interpretH $ \case+runResourceInIO finish = interpretH $ \case Bracket alloc dealloc use -> do a <- runT alloc d <- bindT dealloc u <- bindT use - let runIt :: Sem (Resource ': r) x -> IO x- runIt = finish .@ runResource+ let run_it :: Sem (Resource ': r) x -> IO x+ run_it = finish .@ runResourceInIO_b - sendM $ X.bracket (runIt a) (runIt . d) (runIt . u)+ sendM $ X.bracket (run_it a) (run_it . d) (run_it . u)++ BracketOnError alloc dealloc use -> do+ a <- runT alloc+ d <- bindT dealloc+ u <- bindT use++ let run_it :: Sem (Resource ': r) x -> IO x+ run_it = finish .@ runResourceInIO_b++ sendM $ X.bracketOnError (run_it a) (run_it . d) (run_it . u)+++------------------------------------------------------------------------------+-- | Run a 'Resource' effect purely.+--+-- @since 0.4.0.0+runResource+ :: ∀ r a+ . Sem (Resource ': r) a+ -> Sem r a+runResource = interpretH $ \case+ Bracket alloc dealloc use -> do+ a <- runT alloc+ d <- bindT dealloc+ u <- bindT use++ let run_it = raise . runResource_b+ resource <- run_it a+ result <- run_it $ u resource+ _ <- run_it $ d resource+ pure result++ BracketOnError alloc dealloc use -> do+ a <- runT alloc+ d <- bindT dealloc+ u <- bindT use++ let run_it = raise . runResource_b++ resource <- run_it a+ result <- run_it $ u resource++ ins <- getInspectorT+ case inspect ins result of+ Just _ -> pure result+ Nothing -> do+ _ <- run_it $ d resource+ pure result+{-# INLINE runResource #-}+++runResource_b+ :: ∀ r a+ . Sem (Resource ': r) a+ -> Sem r a+runResource_b = runResource+{-# NOINLINE runResource_b #-}++runResourceInIO_b+ :: ∀ r a+ . Member (Lift IO) r+ => (∀ x. Sem r x -> IO x)+ -> Sem (Resource ': r) a+ -> Sem r a+runResourceInIO_b = runResourceInIO+{-# NOINLINE runResourceInIO_b #-}
+ test/BracketSpec.hs view
@@ -0,0 +1,82 @@+module BracketSpec where++import Polysemy+import Polysemy.Error+import Polysemy.Output+import Polysemy.Resource+import Polysemy.State+import Polysemy.Trace+import Test.Hspec+++runTest+ :: Sem '[Error (), Resource, State [Char], Trace, Output String] a+ -> ([String], ([Char], Either () a))+runTest = run+ . runFoldMapOutput @String (:[])+ . runTraceAsOutput+ . runState ""+ . runResource+ . runError @()+++spec :: Spec+spec = do+ describe "pure bracket" $ do+ it "persist state and call the finalizer" $ do+ let (ts, (s, e)) = runTest $ do+ bracket+ (put "allocated" >> pure ())+ (\() -> do+ get >>= trace+ put "finalized"+ )+ (\() -> do+ get >>= trace+ put "starting block"+ _ <- throw ()+ put "don't get here"+ )+ ts `shouldContain` ["allocated"]+ ts `shouldContain` ["starting block"]+ s `shouldBe` "finalized"+ e `shouldBe` Left ()++ describe "pure bracketOnError" $ do+ it "persist state and call the finalizer if there was an error" $ do+ let (ts, (s, e)) = runTest $ do+ bracketOnError+ (put "allocated" >> pure ())+ (\() -> do+ get >>= trace+ put "finalized"+ )+ (\() -> do+ get >>= trace+ put "starting block"+ _ <- throw ()+ put "don't get here"+ )+ ts `shouldContain` ["allocated"]+ ts `shouldContain` ["starting block"]+ s `shouldBe` "finalized"+ e `shouldBe` Left ()++ it "should not call the finalizer if there no error" $ do+ let (ts, (s, e)) = runTest $ do+ bracketOnError+ (put "allocated" >> pure ())+ (\() -> do+ get >>= trace+ put "finalized"+ )+ (\() -> do+ get >>= trace+ put "starting block"+ put "don't get here"+ )+ ts `shouldContain` ["allocated"]+ ts `shouldNotContain` ["starting block"]+ s `shouldBe` "don't get here"+ e `shouldBe` Right ()+