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 +4/−0
- effectful-core.cabal +1/−1
- src/Effectful.hs +19/−18
- src/Effectful/Dispatch/Dynamic.hs +87/−19
- src/Effectful/Internal/Monad.hs +21/−5
- src/Effectful/Internal/Unlift.hs +2/−1
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