packages feed

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 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