effectful-core 2.1.0.0 → 2.2.0.0
raw patch · 11 files changed
+212/−30 lines, 11 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ Effectful.Dispatch.Static.Primitive: restoreEnv :: Env es -> Env es -> IO ()
+ Effectful.Internal.Env: restoreEnv :: Env es -> Env es -> IO ()
+ Effectful.Internal.Monad: [:<|>:] :: m a -> m a -> NonDet m a
+ Effectful.Internal.Monad: [Empty] :: NonDet m a
+ Effectful.Internal.Monad: data NonDet :: Effect
+ Effectful.Internal.Monad: data PrimStateEff
+ Effectful.Internal.Monad: instance (Effectful.Internal.Monad.NonDet Effectful.Internal.Effect.:> es) => GHC.Base.Alternative (Effectful.Internal.Monad.Eff es)
+ Effectful.Internal.Monad: instance (Effectful.Internal.Monad.NonDet Effectful.Internal.Effect.:> es) => GHC.Base.MonadPlus (Effectful.Internal.Monad.Eff es)
+ Effectful.NonDet: (<|>) :: Alternative f => f a -> f a -> f a
+ Effectful.NonDet: OnEmptyKeep :: OnEmptyPolicy
+ Effectful.NonDet: OnEmptyRollback :: OnEmptyPolicy
+ Effectful.NonDet: [:<|>:] :: m a -> m a -> NonDet m a
+ Effectful.NonDet: [Empty] :: NonDet m a
+ Effectful.NonDet: class Applicative f => Alternative (f :: Type -> Type)
+ Effectful.NonDet: data CallStack
+ Effectful.NonDet: data NonDet :: Effect
+ Effectful.NonDet: data OnEmptyPolicy
+ Effectful.NonDet: empty :: Alternative f => f a
+ Effectful.NonDet: emptyEff :: (HasCallStack, NonDet :> es) => Eff es a
+ Effectful.NonDet: getCallStack :: CallStack -> [([Char], SrcLoc)]
+ Effectful.NonDet: infixl 3 <|>
+ Effectful.NonDet: instance GHC.Classes.Eq Effectful.NonDet.OnEmptyPolicy
+ Effectful.NonDet: instance GHC.Classes.Ord Effectful.NonDet.OnEmptyPolicy
+ Effectful.NonDet: instance GHC.Generics.Generic Effectful.NonDet.OnEmptyPolicy
+ Effectful.NonDet: instance GHC.Show.Show Effectful.NonDet.OnEmptyPolicy
+ Effectful.NonDet: many :: Alternative f => f a -> f [a]
+ Effectful.NonDet: prettyCallStack :: CallStack -> String
+ Effectful.NonDet: runNonDet :: OnEmptyPolicy -> Eff (NonDet : es) a -> Eff es (Either CallStack a)
+ Effectful.NonDet: some :: Alternative f => f a -> f [a]
+ Effectful.NonDet: sumEff :: (HasCallStack, Foldable t, NonDet :> es) => t (Eff es a) -> Eff es a
+ Effectful.NonDet: type HasCallStack = ?callStack :: CallStack
+ Effectful.Prim: data PrimStateEff
- Effectful.Internal.Effect: type Type = Type
+ Effectful.Internal.Effect: type Type = TYPE LiftedRep
Files
- CHANGELOG.md +8/−0
- README.md +23/−19
- effectful-core.cabal +3/−2
- src/Effectful.hs +1/−1
- src/Effectful/Dispatch/Dynamic.hs +3/−3
- src/Effectful/Dispatch/Static/Primitive.hs +1/−0
- src/Effectful/Internal/Effect.hs +1/−0
- src/Effectful/Internal/Env.hs +23/−0
- src/Effectful/Internal/Monad.hs +36/−5
- src/Effectful/NonDet.hs +112/−0
- src/Effectful/Prim.hs +1/−0
CHANGELOG.md view
@@ -1,3 +1,11 @@+# effectful-core-2.2.0.0 (2022-10-24)+* Change `PrimState` for `Eff` from `RealWorld` to `PrimStateEff` to prevent the+ `Prim` effect from executing arbitrary `IO` actions via `ioToPrim`.+* Deprecate `(:>>)` as [GHC can't efficiently deal with type+ families](https://github.com/haskell-effectful/effectful/issues/52#issuecomment-1269155485).+* Add support for the `Alternative` and `MonadPlus` instances for `Eff` via the+ `NonDet` effect.+ # effectful-core-2.1.0.0 (2022-08-22) * Include the `e :> localEs` constraint in the `EffectHandler` to allow more flexibility in handling higher order effects.
README.md view
@@ -49,19 +49,11 @@ things that matter instead of reinventing all kinds of wheels, hence being a necessary condition for broader adoption of the library. -However, `eff` uses delimited continuations underneath, which:--- Are not yet supported by GHC (though [the-proposal](https://github.com/ghc-proposals/ghc-proposals/pull/313) for including-support for them has been accepted).--- Are quite hard to understand.--- Make the library "too powerful" in a sense as it faces- [a](https://github.com/hasura/eff/issues/13)- [few](https://github.com/hasura/eff/issues/7)- [issues](https://github.com/hasura/eff/issues/12) with no clear path towards- their resolution.+Unfortunately, the development of `eff` has stalled due to a+[few](https://github.com/hasura/eff/issues/13)+[subtle](https://github.com/hasura/eff/issues/7)+[issues](https://github.com/hasura/eff/issues/12) related to its use of+delimited continuations underneath. ### What about `mtl`? @@ -101,8 +93,19 @@ ### Any downsides? -As always, there's no free lunch. The `Eff` monad doesn't support `NonDet` nor-`Coroutine` effects. However, the `NonDet` effect in existing libraries is+As always, there's no free lunch. The `Eff` monad doesn't support effect+handlers that require the ability to suspend or capture the rest of the+computation and resume it later (potentially multiple times). This prevents+`effectful` from providing (in particular):++- A `NonDet` effect handler that executes multiple+[`Alternative`](https://hackage.haskell.org/package/base/docs/Control-Applicative.html#t:Alternative)+branches and collects their results.++- A `Coroutine` effect.++It needs to be noted however that such `NonDet` effect handler in existing+libraries is [broken](https://github.com/lexi-lambda/eff/blob/8c4df4bf54faf22456354be18095b14825be5e85/notes/semantics-zoo.md) and none of the ones with support for higher order effects provide the `Coroutine` effect, so arguably it's not a big loss.@@ -110,7 +113,7 @@ If you need such capability in your application, there are well established libraries such as [conduit](https://hackage.haskell.org/package/conduit) or [list-t](https://hackage.haskell.org/package/list-t) that can be used with-`effectful` without any issues.+`effectful` without any hassle. ### Summary @@ -147,9 +150,10 @@ ## Example -A `Filesystem` effect with two handlers, one that runs in `IO` and another that-uses an in-memory virtual file system can be found-[here](https://github.com/haskell-effectful/effectful/blob/master/effectful/examples/FileSystem.hs).+For the examples see the *Introduction* sections of+[`Effectful.Dispatch.Dynamic`](https://hackage.haskell.org/package/effectful-core/docs/Effectful-Dispatch-Dynamic.html)+and+[`Effectful.Dispatch.Static`](https://hackage.haskell.org/package/effectful-core/docs/Effectful-Dispatch-Static.html). ## Resources
effectful-core.cabal view
@@ -1,7 +1,7 @@ cabal-version: 2.4 build-type: Simple name: effectful-core-version: 2.1.0.0+version: 2.2.0.0 license: BSD-3-Clause license-file: LICENSE category: Control@@ -21,7 +21,7 @@ CHANGELOG.md README.md -tested-with: GHC ==8.8.4 || ==8.10.7 || ==9.0.2 || ==9.2.4 || ==9.4.1+tested-with: GHC ==8.8.4 || ==8.10.7 || ==9.0.2 || ==9.2.4 || ==9.4.2 bug-reports: https://github.com/haskell-effectful/effectful/issues source-repository head@@ -93,6 +93,7 @@ Effectful.Internal.Env Effectful.Internal.Monad Effectful.Internal.Unlift+ Effectful.NonDet Effectful.Prim Effectful.Reader.Dynamic Effectful.Reader.Static
src/Effectful.hs view
@@ -116,7 +116,7 @@ -- $integration -- -- Integration with most of existing libraries and frameworks can be done quite--- easily. The main difference on how that looks like depends on the way a+-- easily. The main difference in how that looks like depends on the way a -- library operates in a monadic context. -- -- There are three main groups a library might fall into. It either operates:
src/Effectful/Dispatch/Dynamic.hs view
@@ -200,9 +200,9 @@ -- -- If an effect makes use of the @m@ parameter, it is a /higher order effect/. ----- Interpretation of higher order effects is slightly more involved. To see why,--- let's consider the @Profiling@ effect for logging how much time a specific--- action took to run:+-- Interpretation of higher order effects is slightly more involving. To see+-- why, let's consider the @Profiling@ effect for logging how much time a+-- specific action took to run: -- -- >>> :{ -- data Profiling :: Effect where
src/Effectful/Dispatch/Static/Primitive.hs view
@@ -31,6 +31,7 @@ -- ** Utils , emptyEnv , cloneEnv+ , restoreEnv , sizeEnv , tailEnv ) where
src/Effectful/Internal/Effect.hs view
@@ -61,6 +61,7 @@ type family xs :>> es :: Constraint where '[] :>> es = () (x : xs) :>> es = (x :> es, xs :>> es)+{-# DEPRECATED (:>>) "Usage of (:>>) slows down GHC too much, so it will be removed in 3.0.0.0. See https://github.com/haskell-effectful/effectful/issues/52#issuecomment-1269155485 for more information." #-} ----------------------------------------
src/Effectful/Internal/Env.hs view
@@ -18,6 +18,7 @@ -- * Operations , emptyEnv , cloneEnv+ , restoreEnv , sizeEnv , tailEnv @@ -156,6 +157,28 @@ relinkEffects storageSize pure $ Env offset refs storage {-# NOINLINE cloneEnv #-}++-- | Restore the environment from its clone.+--+-- @since 2.2.0.0+restoreEnv+ :: Env es -- ^ Destination.+ -> Env es -- ^ Source.+ -> IO ()+restoreEnv dest src = do+ destStorage <- readIORef (envStorage dest)+ srcStorage <- readIORef (envStorage src)+ let destStorageSize = stSize destStorage+ srcStorageSize = stSize srcStorage+ when (destStorageSize /= srcStorageSize) $ do+ error $ "destStorageSize (" ++ show destStorageSize+ ++ ") /= srcStorageSize (" ++ show srcStorageSize ++ ")"+ writeIORef (envStorage dest) $! srcStorage+ -- Decreasing the counter allows leakage of unsafeCoerce (see unsafeCoerce2+ -- in the EnvTests module).+ { stVersion = max (stVersion destStorage) (stVersion srcStorage)+ }+{-# NOINLINE restoreEnv #-} -- | Get the current size of the environment. sizeEnv :: Env es -> IO Int
src/Effectful/Internal/Monad.hs view
@@ -15,6 +15,9 @@ , unsafeEff , unsafeEff_ + -- * NonDet+ , NonDet(..)+ -- * Fail , Fail(..) @@ -24,6 +27,7 @@ -- * Prim , Prim+ , PrimStateEff , runPrim -- * Lifting@@ -74,7 +78,8 @@ , modifyEnv ) where -import Control.Applicative (liftA2)+import Control.Applicative+import Control.Monad import Control.Monad.Base import Control.Monad.Fix import Control.Monad.IO.Class@@ -84,8 +89,9 @@ import Data.Kind (Constraint) import GHC.Exts (oneShot) import GHC.IO (IO(..))-import GHC.Stack (HasCallStack)+import GHC.Stack import System.IO.Unsafe (unsafeDupablePerformIO)+import Unsafe.Coerce (unsafeCoerce) import qualified Control.Exception as E import qualified Control.Monad.Catch as C @@ -222,6 +228,27 @@ mfix f = unsafeEff $ \es -> mfix $ \a -> unEff (f a) es ----------------------------------------+-- NonDet++-- | Provide the ability to use the 'Alternative' and 'MonadPlus' instance of+-- 'Eff'.+--+-- @since 2.2.0.0+data NonDet :: Effect where+ Empty :: NonDet m a+ (:<|>:) :: m a -> m a -> NonDet m a++type instance DispatchOf NonDet = Dynamic++-- | @since: 2.2.0.0+instance NonDet :> es => Alternative (Eff es) where+ empty = withFrozenCallStack (send Empty)+ a <|> b = send (a :<|>: b)++-- | @since: 2.2.0.0+instance NonDet :> es => MonadPlus (Eff es)++---------------------------------------- -- Exception instance C.MonadThrow (Eff es) where@@ -257,7 +284,7 @@ type instance DispatchOf Fail = Dynamic instance Fail :> es => MonadFail (Eff es) where- fail = send . Fail+ fail msg = withFrozenCallStack $ send (Fail msg) ---------------------------------------- -- IO@@ -306,13 +333,17 @@ type instance DispatchOf Prim = Static WithSideEffects data instance StaticRep Prim = Prim +-- | 'PrimState' token for 'Eff'. Used instead of 'RealWorld' to prevent the+-- 'Prim' effect from executing arbitrary 'IO' actions via 'ioToPrim'.+data PrimStateEff+ -- | Run an 'Eff' computation with primitive state-transformer actions. runPrim :: IOE :> es => Eff (Prim : es) a -> Eff es a runPrim = evalStaticRep Prim instance Prim :> es => PrimMonad (Eff es) where- type PrimState (Eff es) = RealWorld- primitive = unsafeEff_ . IO+ type PrimState (Eff es) = PrimStateEff+ primitive = unsafeEff_ . IO . unsafeCoerce ---------------------------------------- -- Lifting
+ src/Effectful/NonDet.hs view
@@ -0,0 +1,112 @@+-- | Provider of the t'Control.Applicative.Alternative' and+-- t'Control.Monad.MonadPlus' instance for 'Eff'.+module Effectful.NonDet+ ( -- * Effect+ NonDet(..)+ , OnEmptyPolicy(..)++ -- ** Handlers+ , runNonDet++ -- * Utils+ , emptyEff+ , sumEff++ -- * Re-exports+ , Alternative(..)+ , HasCallStack+ , CallStack+ , getCallStack+ , prettyCallStack+ ) where++import Control.Applicative+import Data.Coerce+import GHC.Generics+import GHC.Stack++import Effectful+import Effectful.Dispatch.Dynamic+import Effectful.Dispatch.Static+import Effectful.Dispatch.Static.Primitive+import Effectful.Error.Static+import Effectful.Internal.Monad (LocalEnv(..), NonDet(..))++-- | Policy of dealing with modifications to __thread local__ state in the+-- environment in branches that end up calling the 'Empty' operation.+--+-- /Note:/ 'OnEmptyKeep' is significantly faster as there is no need to back up+-- the environment on each call to ':<|>:'.+--+-- @since 2.2.0.0+data OnEmptyPolicy+ = OnEmptyKeep -- ^ Keep modifications on 'Empty'.+ | OnEmptyRollback -- ^ Rollback modifications on 'Empty'.+ deriving (Eq, Generic, Ord, Show)++-- | Run the 'NonDet' effect with a given 'OnEmptyPolicy'.+--+-- /Note:/ ':<|>:' executes the second computation if (and only if) the first+-- computation calls 'Empty'.+--+-- @since 2.2.0.0+runNonDet :: OnEmptyPolicy -> Eff (NonDet : es) a -> Eff es (Either CallStack a)+runNonDet = \case+ OnEmptyKeep -> runNonDetKeep+ OnEmptyRollback -> runNonDetRollback++runNonDetKeep :: Eff (NonDet : es) a -> Eff es (Either CallStack a)+runNonDetKeep = reinterpret (fmap noError . runError @()) $ \env -> \case+ Empty -> throwError ()+ m1 :<|>: m2 -> localSeqUnlift env $ \unlift -> do+ mr <- (Just <$> unlift m1) `catchError` \_ () -> pure Nothing+ case mr of+ Just r -> pure r+ Nothing -> unlift m2++runNonDetRollback :: Eff (NonDet : es) a -> Eff es (Either CallStack a)+runNonDetRollback = reinterpret (fmap noError . runError @()) $ \env -> \case+ Empty -> throwError ()+ m1 :<|>: m2 -> do+ backupEnv <- cloneLocalEnv env+ localSeqUnlift env $ \unlift -> do+ mr <- (Just <$> unlift m1) `catchError` \_ () -> do+ -- If m1 failed, roll back the environment.+ restoreLocalEnv env backupEnv+ pure Nothing+ case mr of+ Just r -> pure r+ Nothing -> unlift m2++----------------------------------------++-- | Specialized version of 'empty' with the 'HasCallStack' constraint for+-- tracking purposes.+--+-- @since 2.2.0.0+emptyEff :: (HasCallStack, NonDet :> es) => Eff es a+emptyEff = withFrozenCallStack $ send Empty++-- | Specialized version of 'asum' with the 'HasCallStack' constraint for+-- tracking purposes.+--+-- @since 2.2.0.0+sumEff :: (HasCallStack, Foldable t, NonDet :> es) => t (Eff es a) -> Eff es a+sumEff = foldr (<|>) emptyEff++----------------------------------------+-- Internal helpers++noError :: Either (cs, e) a -> Either cs a+noError = either (Left . fst) Right++cloneLocalEnv+ :: LocalEnv localEs handlerEs+ -> Eff es (LocalEnv localEs handlerEs)+cloneLocalEnv = coerce . unsafeEff_ . cloneEnv . coerce++restoreLocalEnv+ :: LocalEnv localEs handlerEs+ -> LocalEnv localEs handlerEs+ -> Eff es ()+restoreLocalEnv dest src = unsafeEff_ $ restoreEnv (coerce dest) (coerce src)
src/Effectful/Prim.hs view
@@ -2,6 +2,7 @@ module Effectful.Prim ( -- * Effect Prim+ , PrimStateEff -- ** Handlers , runPrim