packages feed

effectful-core 1.1.0.0 → 1.2.0.0

raw patch · 6 files changed

+134/−44 lines, 6 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Effectful.Dispatch.Dynamic: type family SuffixOf (es :: [Effect]) (baseEs :: [Effect]) :: Constraint
+ Effectful: raiseWith :: HasCallStack => UnliftStrategy -> ((forall r. Eff (e : es) r -> Eff es r) -> Eff es a) -> Eff (e : es) a
+ Effectful.Dispatch.Dynamic: class SharedSuffix (es1 :: [Effect]) (es2 :: [Effect])
+ Effectful.Dispatch.Dynamic: instance (TypeError ...) => Effectful.Dispatch.Dynamic.SharedSuffix '[] '[]
+ Effectful.Dispatch.Dynamic: instance Effectful.Dispatch.Dynamic.SharedSuffix es es
+ Effectful.Dispatch.Dynamic: instance Effectful.Dispatch.Dynamic.SharedSuffix es1 es2 => Effectful.Dispatch.Dynamic.SharedSuffix (e : es1) es2
+ Effectful.Dispatch.Dynamic: instance Effectful.Dispatch.Dynamic.SharedSuffix es1 es2 => Effectful.Dispatch.Dynamic.SharedSuffix es1 (e : es2)
+ Effectful.Internal.Monad: raiseWith :: HasCallStack => UnliftStrategy -> ((forall r. Eff (e : es) r -> Eff es r) -> Eff es a) -> Eff (e : es) a
- Effectful.Dispatch.Dynamic: localLiftUnlift :: (HasCallStack, SuffixOf es handlerEs) => LocalEnv localEs handlerEs -> UnliftStrategy -> ((forall r. Eff es r -> Eff localEs r) -> (forall r. Eff localEs r -> Eff es r) -> Eff es a) -> Eff es a
+ Effectful.Dispatch.Dynamic: localLiftUnlift :: (HasCallStack, SharedSuffix es handlerEs) => LocalEnv localEs handlerEs -> UnliftStrategy -> ((forall r. Eff es r -> Eff localEs r) -> (forall r. Eff localEs r -> Eff es r) -> Eff es a) -> Eff es a
- Effectful.Dispatch.Dynamic: localLiftUnliftIO :: (HasCallStack, SuffixOf es handlerEs, IOE :> es) => LocalEnv localEs handlerEs -> UnliftStrategy -> ((forall r. IO r -> Eff localEs r) -> (forall r. Eff localEs r -> IO r) -> IO a) -> Eff es a
+ Effectful.Dispatch.Dynamic: localLiftUnliftIO :: (HasCallStack, SharedSuffix es handlerEs, IOE :> es) => LocalEnv localEs handlerEs -> UnliftStrategy -> ((forall r. IO r -> Eff localEs r) -> (forall r. Eff localEs r -> IO r) -> IO a) -> Eff es a
- Effectful.Dispatch.Dynamic: localSeqUnlift :: (HasCallStack, SuffixOf es handlerEs) => LocalEnv localEs handlerEs -> ((forall r. Eff localEs r -> Eff es r) -> Eff es a) -> Eff es a
+ Effectful.Dispatch.Dynamic: localSeqUnlift :: (HasCallStack, SharedSuffix es handlerEs) => LocalEnv localEs handlerEs -> ((forall r. Eff localEs r -> Eff es r) -> Eff es a) -> Eff es a
- Effectful.Dispatch.Dynamic: localSeqUnliftIO :: (HasCallStack, SuffixOf es handlerEs, IOE :> es) => LocalEnv localEs handlerEs -> ((forall r. Eff localEs r -> IO r) -> IO a) -> Eff es a
+ Effectful.Dispatch.Dynamic: localSeqUnliftIO :: (HasCallStack, SharedSuffix es handlerEs, IOE :> es) => LocalEnv localEs handlerEs -> ((forall r. Eff localEs r -> IO r) -> IO a) -> Eff es a
- Effectful.Dispatch.Dynamic: localUnlift :: (HasCallStack, SuffixOf es handlerEs) => LocalEnv localEs handlerEs -> UnliftStrategy -> ((forall r. Eff localEs r -> Eff es r) -> Eff es a) -> Eff es a
+ Effectful.Dispatch.Dynamic: localUnlift :: (HasCallStack, SharedSuffix es handlerEs) => LocalEnv localEs handlerEs -> UnliftStrategy -> ((forall r. Eff localEs r -> Eff es r) -> Eff es a) -> Eff es a
- Effectful.Dispatch.Dynamic: localUnliftIO :: (HasCallStack, SuffixOf es handlerEs, IOE :> es) => LocalEnv localEs handlerEs -> UnliftStrategy -> ((forall r. Eff localEs r -> IO r) -> IO a) -> Eff es a
+ Effectful.Dispatch.Dynamic: localUnliftIO :: (HasCallStack, SharedSuffix es handlerEs, IOE :> es) => LocalEnv localEs handlerEs -> UnliftStrategy -> ((forall r. Eff localEs r -> IO r) -> IO a) -> Eff es a
- Effectful.Dispatch.Dynamic: withLiftMap :: (HasCallStack, SuffixOf es handlerEs) => LocalEnv localEs handlerEs -> ((forall a b. (Eff es a -> Eff es b) -> Eff localEs a -> Eff localEs b) -> Eff es r) -> Eff es r
+ Effectful.Dispatch.Dynamic: withLiftMap :: (HasCallStack, SharedSuffix es handlerEs) => LocalEnv localEs handlerEs -> ((forall a b. (Eff es a -> Eff es b) -> Eff localEs a -> Eff localEs b) -> Eff es r) -> Eff es r
- Effectful.Dispatch.Dynamic: withLiftMapIO :: (HasCallStack, SuffixOf es handlerEs, IOE :> es) => LocalEnv localEs handlerEs -> ((forall a b. (IO a -> IO b) -> Eff localEs a -> Eff localEs b) -> Eff es r) -> Eff es r
+ Effectful.Dispatch.Dynamic: withLiftMapIO :: (HasCallStack, SharedSuffix es handlerEs, IOE :> es) => LocalEnv localEs handlerEs -> ((forall a b. (IO a -> IO b) -> Eff localEs a -> Eff localEs b) -> Eff es r) -> Eff es r

Files

CHANGELOG.md view
@@ -1,3 +1,7 @@+# effectful-core-1.2.0.0 (2022-07-28)+* Change `SuffixOf` to `SharedSuffix` and make it behave as advertised.+* Add `raiseWith`.+ # effectful-core-1.1.0.0 (2022-07-19) * Don't reset the `UnliftStrategy` to `SeqUnlift` inside the continuation of   `withEffToIO`.
effectful-core.cabal view
@@ -1,7 +1,7 @@ cabal-version:      2.4 build-type:         Simple name:               effectful-core-version:            1.1.0.0+version:            1.2.0.0 license:            BSD-3-Clause license-file:       LICENSE category:           Control
src/Effectful.hs view
@@ -48,6 +48,7 @@      -- ** Lifting   , raise+  , raiseWith   , subsume   , inject   , Subset@@ -108,8 +109,8 @@ -- - Utilities for defining new effects and interpreting them, possibly in terms --   of already existing ones. ----- While basic effects can be used out of the box, it's usually recommended to--- create your own that serve a more specific purpose.+-- While basic effects can be used out of the box, in general it's recommended+-- to create your own that serve a more specific purpose. --  -- $integration@@ -146,8 +147,8 @@ -- such as t'Control.Monad.Catch.MonadMask' or 'MonadUnliftIO'. -- -- In case the 'Eff' monad doesn't provide a specific instance out of the box,--- it can be supplied via a specific effect. As an example see how the instance--- of @MonadResource@ for 'Eff' is implemented in the+-- it can be supplied via an effect. As an example see how the instance of+-- @MonadResource@ for 'Eff' is implemented in the -- [resourcet-effectful](https://hackage.haskell.org/package/resourcet-effectful) -- package. --@@ -157,22 +158,23 @@ -- If a library operates in 'IO', there are a couple of ways to integrate it. -- -- The easiest way is to use its functions selectively in the 'Eff' monad with--- the help of 'liftIO' or 'withEffToIO' / 'withRunInIO'. This is the easiest--- way. However, it's not particularly robust, since it vastly broadens the--- scope in which the 'IOE' effect is needed (not to mention that explicit--- lifting is annoying).+-- the help of 'liftIO' or 'withEffToIO' / 'withRunInIO'. However, this is not+-- particularly robust, since it vastly broadens the scope in which the 'IOE'+-- effect is needed (not to mention that explicit lifting is annoying). -- -- A somewhat better approach is to create a dummy static effect with -- lightweight wrappers of the library functions. As an example have a look at--- the @Effectful.Concurrent.Async@ module from the--- [effectful](https://hackage.haskell.org/package/effectful) package that wraps--- the API of the [async](https://hackage.haskell.org/package/async)--- package. Unfortunately, this requires the amount of work proportional to the--- size of the library and might not be the best option, especially if you only--- need to make use of a few functions.+-- the+-- [@Effectful.Concurrent.Async@](https://hackage.haskell.org/package/effectful/docs/Effectful-Concurrent-Async.html)+-- module from the [effectful](https://hackage.haskell.org/package/effectful)+-- package that wraps the API of the+-- [async](https://hackage.haskell.org/package/async) package. Unfortunately,+-- this requires the amount of work proportional to the size of the library and+-- might not be the best option, especially if you only need to make use of a+-- tiny portion of the API. -- -- Even better (though sometimes hard to do in practice) way is to consider,--- what the library will be used for and then create a custom effect with high+-- what do you need the library for and then create a custom effect with high -- level operations that the library in question will help us implement. The -- advantage of this approach is that we're hiding implementation details from -- the so-called "business logic" of our application and make it possible to@@ -186,8 +188,7 @@ -- [servant-server](https://hackage.haskell.org/package/servant-server) package. -- -- In such case it's best to mirror the monad in question by the 'Eff' monad--- with appropriate effects (as tha majority of popular monad transformers have--- [subtle+-- with appropriate effects (as most popular monad transformers have [subtle -- issues](https://github.com/haskell-effectful/effectful/blob/master/transformers.md)), -- use it as soon as possible, then at the end feed the final state to the monad -- of the library so it proceeds as if nothing unusual happened.@@ -230,5 +231,5 @@ -- Libraries working in a polymorphic monad use @mtl@ style effects. Details -- about their integration with the 'Eff' monad require familiarity with -- dynamically dispatched effects and thus are available in the--- "Effectful.Dispatch.Dynamic#g:integration" module.+-- "Effectful.Dispatch.Dynamic#integration" module. --
src/Effectful/Dispatch/Dynamic.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE UndecidableInstances #-} -- | Dynamically dispatched effects. module Effectful.Dispatch.Dynamic   ( -- * Introduction@@ -9,7 +10,7 @@     -- ** First order and higher order effects     -- $order -    -- ** Integration with @mtl@ style effects #integration#+    -- ** Integration with @mtl@ style effects     -- $integration      -- * Sending operations to the handler@@ -40,7 +41,7 @@   , localLiftUnliftIO      -- *** Utils-  , SuffixOf+  , SharedSuffix      -- * Re-exports   , HasCallStack@@ -48,8 +49,8 @@  import Control.Exception (bracket) import Control.Monad.IO.Unlift-import Data.Kind import GHC.Stack (HasCallStack)+import GHC.TypeLits  import Effectful.Internal.Effect import Effectful.Internal.Env@@ -117,8 +118,10 @@ -- :} -- -- /Note:/ the above functions and the 'DispatchOf' instance can also be--- automatically generated by the @makeEffect@ function from the @effectful-th@--- library.+-- automatically generated by the+-- [@makeEffect@](https://hackage.haskell.org/package/effectful-th/docs/Effectful-TH.html#v:makeEffect)+-- function from the+-- [effectful-th](https://hackage.haskell.org/package/effectful-th) package. -- -- The following defines an 'EffectHandler' that reads and writes files from the -- drive:@@ -275,6 +278,8 @@  -- $integration --+-- #integration#+-- -- There exists a lot of libraries that provide their functionality as an @mtl@ -- style effect, which generally speaking is a type class that contains core -- operations of the library in question.@@ -432,7 +437,7 @@ -- | Create a local unlifting function with the 'SeqUnlift' strategy. For the -- general version see 'localUnlift'. localSeqUnlift-  :: (HasCallStack, SuffixOf es handlerEs)+  :: (HasCallStack, SharedSuffix es handlerEs)   => LocalEnv localEs handlerEs   -- ^ Local environment.   -> ((forall r. Eff localEs r -> Eff es r) -> Eff es a)@@ -445,7 +450,7 @@ -- | Create a local unlifting function with the 'SeqUnlift' strategy. For the -- general version see 'localUnliftIO'. localSeqUnliftIO-  :: (HasCallStack, SuffixOf es handlerEs, IOE :> es)+  :: (HasCallStack, SharedSuffix es handlerEs, IOE :> es)   => LocalEnv localEs handlerEs   -- ^ Local environment.   -> ((forall r. Eff localEs r -> IO r) -> IO a)@@ -455,7 +460,7 @@  -- | Create a local unlifting function with the given strategy. localUnlift-  :: (HasCallStack, SuffixOf es handlerEs)+  :: (HasCallStack, SharedSuffix es handlerEs)   => LocalEnv localEs handlerEs   -- ^ Local environment.   -> UnliftStrategy@@ -472,7 +477,7 @@  -- | Create a local unlifting function with the given strategy. localUnliftIO-  :: (HasCallStack, SuffixOf es handlerEs, IOE :> es)+  :: (HasCallStack, SharedSuffix es handlerEs, IOE :> es)   => LocalEnv localEs handlerEs   -- ^ Local environment.   -> UnliftStrategy@@ -494,7 +499,7 @@ -- /Note:/ the computation must not run its argument in a different thread, -- attempting to do so will result in a runtime error. withLiftMap-  :: (HasCallStack, SuffixOf es handlerEs)+  :: (HasCallStack, SharedSuffix es handlerEs)   => LocalEnv localEs handlerEs   -- ^ Local environment.   -> ((forall a b. (Eff es a -> Eff es b) -> Eff localEs a -> Eff localEs b) -> Eff es r)@@ -534,7 +539,7 @@ --     forkIOWithUnmask $ \unmask -> unlift $ m $ liftMap unmask -- :} withLiftMapIO-  :: (HasCallStack, SuffixOf es handlerEs, IOE :> es)+  :: (HasCallStack, SharedSuffix es handlerEs, IOE :> es)   => LocalEnv localEs handlerEs   -- ^ Local environment.   -> ((forall a b. (IO a -> IO b) -> Eff localEs a -> Eff localEs b) -> Eff es r)@@ -556,7 +561,7 @@ -- /Note:/ depending on the computation you're lifting 'localUnlift' along with -- 'withLiftMap' might be enough and is more efficient. localLiftUnlift-  :: (HasCallStack, SuffixOf es handlerEs)+  :: (HasCallStack, SharedSuffix es handlerEs)   => LocalEnv localEs handlerEs   -- ^ Local environment.   -> UnliftStrategy@@ -582,7 +587,7 @@ -- /Note:/ depending on the computation you're lifting 'localUnliftIO' along -- with 'withLiftMapIO' might be enough and is more efficient. localLiftUnliftIO-  :: (HasCallStack, SuffixOf es handlerEs, IOE :> es)+  :: (HasCallStack, SharedSuffix es handlerEs, IOE :> es)   => LocalEnv localEs handlerEs   -- ^ Local environment.   -> UnliftStrategy@@ -596,13 +601,76 @@ ---------------------------------------- -- Utils --- | Require that the second list of effects is a suffix of the first one.+-- | Require that both effect stacks share an opaque suffix. ----- In other words, 'SuffixOf' @es@ @baseEs@ means "a suffix of @es@ is--- @baseEs@".-type family SuffixOf (es :: [Effect]) (baseEs :: [Effect]) :: Constraint where-  SuffixOf   baseEs baseEs = ()-  SuffixOf (e : es) baseEs = SuffixOf es baseEs+-- Functions from the 'localUnlift' family utilize this constraint to guarantee+-- sensible usage of unlifting functions.+--+-- As an example, consider the following higher order effect:+--+-- >>> :{+--   data E :: Effect where+--     E :: m a -> E m a+--   type instance DispatchOf E = Dynamic+-- :}+--+-- Running local actions in a more specific environment is fine:+--+-- >>> :{+--  runE1 :: Eff (E ': es) a -> Eff es a+--  runE1 = interpret $ \env -> \case+--    E m -> runReader () $ do+--      localSeqUnlift env $ \unlift -> unlift m+-- :}+--+-- Running local actions in a more general environment is fine:+--+-- >>> :{+--  runE2 :: Eff (E ': es) a -> Eff es a+--  runE2 = reinterpret (runReader ()) $ \env -> \case+--    E m -> raise $ do+--      localSeqUnlift env $ \unlift -> unlift m+-- :}+--+-- However, running local actions in an unrelated environment is not fine as+-- this would make it possible to run anything within 'runPureEff':+--+-- >>> :{+--  runE3 :: Eff (E ': es) a -> Eff es a+--  runE3 = reinterpret (runReader ()) $ \env -> \case+--    E m -> pure . runPureEff $ do+--      localSeqUnlift env $ \unlift -> unlift m+-- :}+-- ...+-- ...Could not deduce (SharedSuffix '[] es)...+-- ...+--+-- Running local actions in a monomorphic effect stack is also not fine as+-- this makes a special case of the above possible:+--+-- >>> :{+--  runE4 :: Eff '[E, IOE] a -> Eff '[IOE] a+--  runE4 = interpret $ \env -> \case+--    E m -> pure . runPureEff $ do+--      localSeqUnlift env $ \unlift -> unlift m+-- :}+-- ...+-- ...Running local actions in monomorphic effect stacks is not supported...+-- ...+--+class SharedSuffix (es1 :: [Effect]) (es2 :: [Effect]) +instance {-# INCOHERENT #-} SharedSuffix es es+instance {-# INCOHERENT #-} SharedSuffix es1 es2 => SharedSuffix (e : es1) es2+instance {-# INCOHERENT #-} SharedSuffix es1 es2 => SharedSuffix es1 (e : es2)++-- | This is always preferred to @SharedSuffix es es@ as it's not incoherent.+instance+  TypeError+  ( Text "Running local actions in monomorphic effect stacks is not supported." :$$:+    Text "As a solution simply change the stack to have a polymorphic suffix."+  ) => SharedSuffix '[] '[]+ -- $setup -- >>> import Control.Concurrent (ThreadId, forkIOWithUnmask)+-- >>> import Effectful.Reader.Static
src/Effectful/Internal/Monad.hs view
@@ -28,6 +28,7 @@    -- * Lifting   , raise+  , raiseWith   , subsume   , inject @@ -319,13 +320,27 @@ -- Lifting  -- | Lift an 'Eff' computation into an effect stack with one more effect.------ /Note:/ unless you need this function rarely, you're most likely working with--- monomorphic effect stacks, which is not recommended (see 'Eff' for more--- information). raise :: Eff es a -> Eff (e : es) a raise m = unsafeEff $ \es -> unEff m =<< tailEnv es +-- | Lift an 'Eff' computation into an effect stack with one more effect and+-- create an unlifting function with the given strategy.+raiseWith+  :: HasCallStack+  => UnliftStrategy+  -> ((forall r. Eff (e : es) r -> Eff es r) -> Eff es a)+  -- ^ Continuation with the unlifting function in scope.+  -> Eff (e : es) a+raiseWith strategy k = case strategy of+  SeqUnlift -> unsafeEff $ \ees -> do+    es <- tailEnv ees+    seqUnliftIO ees $ \unlift -> do+      (`unEff` es) $ k $ unsafeEff_ . unlift+  ConcUnlift p l -> unsafeEff $ \ees -> do+    es <- tailEnv ees+    concUnliftIO ees p l $ \unlift -> do+      (`unEff` es) $ k $ unsafeEff_ . unlift+ -- | Eliminate a duplicate effect from the top of the effect stack. subsume :: e :> es => Eff (e : es) a -> Eff es a subsume m = unsafeEff $ \es0 -> do@@ -355,7 +370,8 @@ -- handler. -- -- The second type variable represents effects of a handler and is needed for--- technical reasons to guarantee soundness.+-- technical reasons to guarantee soundness (see+-- t'Effectful.Dispatch.Dynamic.SharedSuffix' for more information). newtype LocalEnv (localEs :: [Effect]) (handlerEs :: [Effect]) = LocalEnv (Env localEs)  -- | Type signature of the effect handler.
src/Effectful/Internal/Unlift.hs view
@@ -40,7 +40,8 @@ -- 'Effectful.Dispatch.Dynamic.localUnlift' family. -- -- /Warning:/ unlifting functions should not be used outside of continuations--- that brought them into scope. __The behavior when doing so is undefined.__+-- that brought them into scope. While this currently works just fine, there are+-- no guarantees it will continue doing so in the future. data UnliftStrategy   = SeqUnlift   -- ^ The fastest strategy and a default setting for t'Effectful.IOE'. An