fused-effects 1.0.0.0 → 1.0.0.1
raw patch · 20 files changed
+82/−93 lines, 20 filesPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
API changes (from Hackage documentation)
+ Control.Algebra: instance Control.Algebra.Algebra sig m => Control.Algebra.Algebra sig (Data.Monoid.Ap m)
+ Control.Algebra: instance Control.Algebra.Algebra sig m => Control.Algebra.Algebra sig (Data.Semigroup.Internal.Alt m)
+ Control.Effect.Catch: class HFunctor sig => Effect sig
+ Control.Effect.Choose: class HFunctor sig => Effect sig
+ Control.Effect.Cull: class HFunctor sig => Effect sig
+ Control.Effect.Cut: class HFunctor sig => Effect sig
+ Control.Effect.Empty: class HFunctor sig => Effect sig
+ Control.Effect.Fail: class HFunctor sig => Effect sig
+ Control.Effect.Fresh: class HFunctor sig => Effect sig
+ Control.Effect.Lift: class HFunctor sig => Effect sig
+ Control.Effect.NonDet: class HFunctor sig => Effect sig
+ Control.Effect.Reader: class HFunctor sig => Effect sig
+ Control.Effect.State: class HFunctor sig => Effect sig
+ Control.Effect.Throw: class HFunctor sig => Effect sig
+ Control.Effect.Trace: class HFunctor sig => Effect sig
+ Control.Effect.Writer: class HFunctor sig => Effect sig
- Control.Algebra: L :: f m k -> (:+:) f g k
+ Control.Algebra: L :: f m k -> (:+:) f g (m :: * -> *) k
- Control.Algebra: R :: g m k -> (:+:) f g k
+ Control.Algebra: R :: g m k -> (:+:) f g (m :: * -> *) k
- Control.Carrier.Interpret: InterpretC :: m a -> InterpretC s m a
+ Control.Carrier.Interpret: InterpretC :: m a -> InterpretC s (sig :: (* -> *) -> * -> *) m a
- Control.Effect.Empty: Empty :: Empty k
+ Control.Effect.Empty: Empty :: Empty (m :: * -> *) k
- Control.Effect.NonDet: Empty :: Empty k
+ Control.Effect.NonDet: Empty :: Empty (m :: * -> *) k
- Control.Effect.Sum: L :: f m k -> (:+:) f g k
+ Control.Effect.Sum: L :: f m k -> (:+:) f g (m :: * -> *) k
- Control.Effect.Sum: R :: g m k -> (:+:) f g k
+ Control.Effect.Sum: R :: g m k -> (:+:) f g (m :: * -> *) k
- Control.Effect.Throw: Throw :: e -> Throw e k
+ Control.Effect.Throw: Throw :: e -> Throw e (m :: * -> *) k
Files
- ChangeLog.md +4/−0
- README.lhs +12/−26
- README.md +12/−26
- benchmark/Bench.hs +7/−38
- fused-effects.cabal +1/−1
- src/Control/Algebra.hs +31/−1
- src/Control/Effect/Catch.hs +1/−0
- src/Control/Effect/Choose.hs +1/−0
- src/Control/Effect/Cull.hs +1/−0
- src/Control/Effect/Cut.hs +1/−0
- src/Control/Effect/Empty.hs +2/−1
- src/Control/Effect/Fail.hs +1/−0
- src/Control/Effect/Fresh.hs +1/−0
- src/Control/Effect/Lift.hs +1/−0
- src/Control/Effect/NonDet.hs +1/−0
- src/Control/Effect/Reader.hs +1/−0
- src/Control/Effect/State.hs +1/−0
- src/Control/Effect/Throw.hs +1/−0
- src/Control/Effect/Trace.hs +1/−0
- src/Control/Effect/Writer.hs +1/−0
ChangeLog.md view
@@ -1,3 +1,7 @@+# v1.0.0.1++- Adds passthrough `Algebra` instances for `Ap` and `Alt`, allowing the invocation of effects inside these structures without extraneous constructor applications.+ # v1.0.0.0 - Adds an `Empty` effect, modelling nondeterminism without choice ([#196](https://github.com/fused-effects/fused-effects/pull/196)).
README.lhs view
@@ -74,7 +74,6 @@ import Control.Carrier.Reader import Control.Carrier.State.Strict import Control.Monad.IO.Class (liftIO)-import qualified Control.Monad.State.Class as MTL main :: IO () main = pure ()@@ -272,12 +271,18 @@ * [`fused-effects-lens`][felens] provides combinators to use the [`lens`][lens] library fluently inside effectful computations. * [`fused-effects-exceptions`][exc] provides handlers for exceptions thrown in the `IO` monad. * [`fused-effects-resumable`][] provides resumable exceptions, which can also serve as a limited form of coroutines.-* [`fused-effects-random`][] provides a `Random` effect integrated into a `fused-effects` stack.+* [`fused-effects-mwc-random`][] provides a performant, high-quality source of random data, as well as values from common numerical distributions.+* [`fused-effects-readline`][] provides a REPL effect that interfaces with [`haskeline`][] for its UI.+* [`fused-effects-parser`][] provides parser-combinator style effects similar to parsing libraries such as [`trifecta`][]. [exc]: https://github.com/fused-effects/fused-effects-exceptions [felens]: http://hackage.haskell.org/package/fused-effects-lens-[`fused-effects-random`]: https://github.com/fused-effects/fused-effects-random+[`fused-effects-mwc-random`]: https://github.com/fused-effects/fused-effects-mwc-random [`fused-effects-resumable`]: https://github.com/fused-effects/fused-effects-resumable+[`fused-effects-readline`]: https://github.com/fused-effects/fused-effects-readline+[`haskeline`]: https://hackage.haskell.org/package/haskeline+[`fused-effects-parser`]: https://github.com/fused-effects/fused-effects-parser+[`trifecta`]: https://hackage.haskell.org/package/trifecta [lens]: http://hackage.haskell.org/package/lens @@ -296,30 +301,11 @@ Also like `mtl`, `fused-effects` allows scoped operations like `local` and `catchError` to be given different interpretations. As with first-order operations, `mtl` achieves this with a final tagless encoding via methods, whereas `fused-effects` achieves this with an initial algebra encoding via `Carrier` instances. -Unlike `mtl`, effects are automatically available regardless of where they occur in the signature; in `mtl` this requires instances for all valid orderings of the transformers (O(n²) of them, in general).--Also unlike `mtl`, there can be more than one `State` or `Reader` effect in a signature. This is a tradeoff: `mtl` is able to provide excellent type inference for effectful operations like `get`, since the functional dependencies can resolve the state type from the monad type. On the other hand, this behaviour can be recovered in `fused-effects` using `newtype` wrappers with phantom type parameters and helper functions, e.g.:--```haskell-newtype Wrapper s m a = Wrapper { runWrapper :: m a }- deriving (Applicative, Functor, Monad)--instance Algebra sig m => Algebra sig (Wrapper s m) where- alg = Wrapper . alg . handleCoercible--getState :: Has (State s) sig m => Wrapper s m s-getState = get-```--Indeed, `Wrapper` can now be made an instance of `MonadState`:+In addition, `mtl` and `fused-effects` are similar in that they provide instances for the monad types defined in the `transformers` package (`Control.Monad.Reader`, `Control.Monad.Writer`, etc). This means that applications using `mtl` can migrate many existing `transformers`-based monad stacks to `fused-effects` with minimal code changes. `fused-effects` provides its own hierarchy of carrier monads (under the `Control.Carrier` namespace) that provide a more fluent interface for new code, though it may be useful to use `transformers` types when working with third-party libraries. -```haskell-instance Has (State s) sig m => MTL.MonadState s (Wrapper s m) where- get = Control.Carrier.State.Strict.get- put = Control.Carrier.State.Strict.put-```+Unlike `mtl`, effects are automatically available regardless of where they occur in the signature; in `mtl` this requires instances for all valid orderings of the transformers (O(n²) of them, in general). -Thus, the approaches aren’t mutually exclusive; consumers are free to decide which approach makes the most sense for their situation.+Also unlike `mtl`, there can be more than one `State` or `Reader` effect in a signature. This is a tradeoff: `mtl` is able to provide excellent type inference for effectful operations like `get`, since the functional dependencies can resolve the state type from the monad type. Unlike `fused-effects`, `mtl` provides a `ContT` monad transformer; however, it’s worth noting that many behaviours possible with delimited continuations (e.g. resumable exceptions) are directly encodable as effects. @@ -345,7 +331,7 @@ Like [`polysemy`](http://hackage.haskell.org/package/polysemy), `fused-effects` is a batteries-included effect system capable of scoped, reinterpretable algebraic effects. -As of GHC 8.8, `fused-effects` outperforms `polysemy`, though new effects take more code to define in `fused-effects` than `polysemy` (though the `Control.Effect.Interpret` effect is suitable for rapid prototyping of new effects). Like `freer-simple` and unlike `fused-effects`, polysemy provides custom type errors if a given effect invocation is ambigous or invalid in the current context.+As of GHC 8.8, `fused-effects` outperforms `polysemy`, though new effects take more code to define in `fused-effects` than `polysemy` (though the `Control.Carrier.Interpret` module provides a low-friction API for rapid prototyping of new effects). Like `freer-simple` and unlike `fused-effects`, polysemy provides custom type errors if a given effect invocation is ambigous or invalid in the current context. #### Comparison to `eff`
README.md view
@@ -74,7 +74,6 @@ import Control.Carrier.Reader import Control.Carrier.State.Strict import Control.Monad.IO.Class (liftIO)-import qualified Control.Monad.State.Class as MTL main :: IO () main = pure ()@@ -272,12 +271,18 @@ * [`fused-effects-lens`][felens] provides combinators to use the [`lens`][lens] library fluently inside effectful computations. * [`fused-effects-exceptions`][exc] provides handlers for exceptions thrown in the `IO` monad. * [`fused-effects-resumable`][] provides resumable exceptions, which can also serve as a limited form of coroutines.-* [`fused-effects-random`][] provides a `Random` effect integrated into a `fused-effects` stack.+* [`fused-effects-mwc-random`][] provides a performant, high-quality source of random data, as well as values from common numerical distributions.+* [`fused-effects-readline`][] provides a REPL effect that interfaces with [`haskeline`][] for its UI.+* [`fused-effects-parser`][] provides parser-combinator style effects similar to parsing libraries such as [`trifecta`][]. [exc]: https://github.com/fused-effects/fused-effects-exceptions [felens]: http://hackage.haskell.org/package/fused-effects-lens-[`fused-effects-random`]: https://github.com/fused-effects/fused-effects-random+[`fused-effects-mwc-random`]: https://github.com/fused-effects/fused-effects-mwc-random [`fused-effects-resumable`]: https://github.com/fused-effects/fused-effects-resumable+[`fused-effects-readline`]: https://github.com/fused-effects/fused-effects-readline+[`haskeline`]: https://hackage.haskell.org/package/haskeline+[`fused-effects-parser`]: https://github.com/fused-effects/fused-effects-parser+[`trifecta`]: https://hackage.haskell.org/package/trifecta [lens]: http://hackage.haskell.org/package/lens @@ -296,30 +301,11 @@ Also like `mtl`, `fused-effects` allows scoped operations like `local` and `catchError` to be given different interpretations. As with first-order operations, `mtl` achieves this with a final tagless encoding via methods, whereas `fused-effects` achieves this with an initial algebra encoding via `Carrier` instances. -Unlike `mtl`, effects are automatically available regardless of where they occur in the signature; in `mtl` this requires instances for all valid orderings of the transformers (O(n²) of them, in general).--Also unlike `mtl`, there can be more than one `State` or `Reader` effect in a signature. This is a tradeoff: `mtl` is able to provide excellent type inference for effectful operations like `get`, since the functional dependencies can resolve the state type from the monad type. On the other hand, this behaviour can be recovered in `fused-effects` using `newtype` wrappers with phantom type parameters and helper functions, e.g.:--```haskell-newtype Wrapper s m a = Wrapper { runWrapper :: m a }- deriving (Applicative, Functor, Monad)--instance Algebra sig m => Algebra sig (Wrapper s m) where- alg = Wrapper . alg . handleCoercible--getState :: Has (State s) sig m => Wrapper s m s-getState = get-```--Indeed, `Wrapper` can now be made an instance of `MonadState`:+In addition, `mtl` and `fused-effects` are similar in that they provide instances for the monad types defined in the `transformers` package (`Control.Monad.Reader`, `Control.Monad.Writer`, etc). This means that applications using `mtl` can migrate many existing `transformers`-based monad stacks to `fused-effects` with minimal code changes. `fused-effects` provides its own hierarchy of carrier monads (under the `Control.Carrier` namespace) that provide a more fluent interface for new code, though it may be useful to use `transformers` types when working with third-party libraries. -```haskell-instance Has (State s) sig m => MTL.MonadState s (Wrapper s m) where- get = Control.Carrier.State.Strict.get- put = Control.Carrier.State.Strict.put-```+Unlike `mtl`, effects are automatically available regardless of where they occur in the signature; in `mtl` this requires instances for all valid orderings of the transformers (O(n²) of them, in general). -Thus, the approaches aren’t mutually exclusive; consumers are free to decide which approach makes the most sense for their situation.+Also unlike `mtl`, there can be more than one `State` or `Reader` effect in a signature. This is a tradeoff: `mtl` is able to provide excellent type inference for effectful operations like `get`, since the functional dependencies can resolve the state type from the monad type. Unlike `fused-effects`, `mtl` provides a `ContT` monad transformer; however, it’s worth noting that many behaviours possible with delimited continuations (e.g. resumable exceptions) are directly encodable as effects. @@ -345,7 +331,7 @@ Like [`polysemy`](http://hackage.haskell.org/package/polysemy), `fused-effects` is a batteries-included effect system capable of scoped, reinterpretable algebraic effects. -As of GHC 8.8, `fused-effects` outperforms `polysemy`, though new effects take more code to define in `fused-effects` than `polysemy` (though the `Control.Effect.Interpret` effect is suitable for rapid prototyping of new effects). Like `freer-simple` and unlike `fused-effects`, polysemy provides custom type errors if a given effect invocation is ambigous or invalid in the current context.+As of GHC 8.8, `fused-effects` outperforms `polysemy`, though new effects take more code to define in `fused-effects` than `polysemy` (though the `Control.Carrier.Interpret` module provides a low-friction API for rapid prototyping of new effects). Like `freer-simple` and unlike `fused-effects`, polysemy provides custom type errors if a given effect invocation is ambigous or invalid in the current context. #### Comparison to `eff`
benchmark/Bench.hs view
@@ -7,8 +7,7 @@ import Control.Carrier.Interpret import Control.Carrier.State.Strict import Control.Carrier.Writer.Strict-import Control.Monad (ap, replicateM_)-import Data.Functor.Identity+import Control.Monad (replicateM_) import Data.Monoid (Sum(..)) import Gauge @@ -18,29 +17,15 @@ main = defaultMain [ NonDet.benchmark , bgroup "WriterC"- [ bgroup "Cod"- [ bench "100" $ whnf (run . runCod pure . execWriter @(Sum Int) . runCod pure . tellLoop) 100- , bench "1000" $ whnf (run . runCod pure . execWriter @(Sum Int) . runCod pure . tellLoop) 1000- , bench "10000" $ whnf (run . runCod pure . execWriter @(Sum Int) . runCod pure . tellLoop) 10000- ]- , bgroup "standalone"- [ bench "100" $ whnf (run . execWriter @(Sum Int) . tellLoop) 100- , bench "1000" $ whnf (run . execWriter @(Sum Int) . tellLoop) 1000- , bench "10000" $ whnf (run . execWriter @(Sum Int) . tellLoop) 10000- ]+ [ bench "100" $ whnf (run . execWriter @(Sum Int) . tellLoop) 100+ , bench "1000" $ whnf (run . execWriter @(Sum Int) . tellLoop) 1000+ , bench "10000" $ whnf (run . execWriter @(Sum Int) . tellLoop) 10000 ] , bgroup "Strict StateC"- [ bgroup "Cod"- [ bench "100" $ whnf (run . runCod pure . execState @(Sum Int) 0 . runCod pure . modLoop) 100- , bench "1000" $ whnf (run . runCod pure . execState @(Sum Int) 0 . runCod pure . modLoop) 1000- , bench "10000" $ whnf (run . runCod pure . execState @(Sum Int) 0 . runCod pure . modLoop) 10000- ]- , bgroup "standalone"- [ bench "100" $ whnf (run . execState @(Sum Int) 0 . modLoop) 100- , bench "1000" $ whnf (run . execState @(Sum Int) 0 . modLoop) 1000- , bench "10000" $ whnf (run . execState @(Sum Int) 0 . modLoop) 10000- ]+ [ bench "100" $ whnf (run . execState @(Sum Int) 0 . modLoop) 100+ , bench "1000" $ whnf (run . execState @(Sum Int) 0 . modLoop) 1000+ , bench "10000" $ whnf (run . execState @(Sum Int) 0 . modLoop) 10000 ] , bgroup "InterpretC vs InterpretStateC vs StateC"@@ -67,19 +52,3 @@ modLoop :: Has (State (Sum Int)) sig m => Int -> m () modLoop i = replicateM_ i (modify (+ (Sum (1 :: Int))))--newtype Cod m a = Cod { unCod :: forall b . (a -> m b) -> m b }- deriving (Functor)--runCod :: (a -> m b) -> Cod m a -> m b-runCod = flip unCod--instance Applicative (Cod m) where- pure a = Cod (\ k -> k a)- (<*>) = ap--instance Monad (Cod m) where- Cod a >>= f = Cod (\ k -> a (runCod k . f))--instance (Algebra sig m, Effect sig) => Algebra sig (Cod m) where- alg op = Cod (\ k -> alg (thread (Identity ()) (runCod (pure . Identity) . runIdentity) op) >>= k . runIdentity)
fused-effects.cabal view
@@ -1,7 +1,7 @@ cabal-version: 2.2 name: fused-effects-version: 1.0.0.0+version: 1.0.0.1 synopsis: A fast, flexible, fused effect system. description: A fast, flexible, fused effect system, à la Effect Handlers in Scope, Monad Transformers and Modular Algebraic Effects: What Binds Them Together, and Fusion for Free—Efficient Algebraic Effect Handlers. homepage: https://github.com/fused-effects/fused-effects
src/Control/Algebra.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE ConstraintKinds, DeriveFunctor, FlexibleInstances, FunctionalDependencies, RankNTypes, TypeOperators, UndecidableInstances #-}+{-# LANGUAGE CPP, ConstraintKinds, DeriveFunctor, FlexibleInstances, FunctionalDependencies, RankNTypes, TypeOperators, UndecidableInstances #-} {- | The 'Algebra' class is the mechanism with which effects are interpreted. @@ -41,6 +41,7 @@ import qualified Control.Monad.Trans.Writer.Lazy as Writer.Lazy import qualified Control.Monad.Trans.Writer.Strict as Writer.Strict import Data.List.NonEmpty (NonEmpty)+import Data.Monoid import qualified Data.Semigroup as S import Data.Tuple (swap) @@ -116,6 +117,35 @@ instance Algebra sig m => Algebra sig (Identity.IdentityT m) where alg = Identity.IdentityT . alg . handleCoercible++#if MIN_VERSION_base(4,12,0)+-- | This instance permits effectful actions to be lifted into the 'Ap' monad+-- given a monoidal return type, which can provide clarity when chaining calls+-- to 'mappend'.+--+-- > mappend <$> act1 <*> (mappend <$> act2 <*> act3)+--+-- is equivalent to+--+-- > getAp (act1 <> act2 <> act3)+--+-- @since 1.0.1.0+instance Algebra sig m => Algebra sig (Ap m) where+ alg = Ap . alg . handleCoercible+#endif++-- | This instance permits effectful actions to be lifted into the 'Alt' monad,+-- which eases the invocation of repeated alternation with '<|>':+--+-- > a <|> b <|> c <|> d+--+-- is equivalent to+--+-- > getAlt (mconcat [a, b, c, d])+--+-- @since 1.0.1.0+instance Algebra sig m => Algebra sig (Alt m) where+ alg = Alt . alg . handleCoercible instance Algebra sig m => Algebra (Reader r :+: sig) (Reader.ReaderT r m) where alg (L (Ask k)) = Reader.ask >>= k
src/Control/Effect/Catch.hs view
@@ -12,6 +12,7 @@ , catchError -- * Re-exports , Algebra+, Effect , Has , run ) where
src/Control/Effect/Choose.hs view
@@ -24,6 +24,7 @@ , Choosing(..) -- * Re-exports , Algebra+, Effect , Has , run ) where
src/Control/Effect/Cull.hs view
@@ -15,6 +15,7 @@ , cull -- * Re-exports , Algebra+, Effect , Has , run ) where
src/Control/Effect/Cut.hs view
@@ -19,6 +19,7 @@ , cut -- * Re-exports , Algebra+, Effect , Has , run ) where
src/Control/Effect/Empty.hs view
@@ -1,6 +1,6 @@ {- | An effect modelling nondeterminism without choice (success or failure). -This can be seen as similar to 'Control.Effect.Fail.Fail', but without an error message. The 'Control.Effect.NonDet.NonDet' effect is the composition of 'Empty' and 'Control.Effect.Choice.Choice'.+This can be seen as similar to 'Control.Effect.Fail.Fail', but without an error message. The 'Control.Effect.NonDet.NonDet' effect is the composition of 'Empty' and 'Control.Effect.Choose.Choose'. Predefined carriers: @@ -17,6 +17,7 @@ , guard -- * Re-exports , Algebra+, Effect , Has , run ) where
src/Control/Effect/Fail.hs view
@@ -18,6 +18,7 @@ , Fail.MonadFail(..) -- * Re-exports , Algebra+, Effect , Has , run ) where
src/Control/Effect/Fresh.hs view
@@ -13,6 +13,7 @@ , fresh -- * Re-exports , Algebra+, Effect , Has , run ) where
src/Control/Effect/Lift.hs view
@@ -20,6 +20,7 @@ , liftWith -- * Re-exports , Algebra+, Effect , Has , run ) where
src/Control/Effect/NonDet.hs view
@@ -20,6 +20,7 @@ -- * Re-exports , Alternative(..) , Algebra+, Effect , Has , MonadPlus(..) , guard
src/Control/Effect/Reader.hs view
@@ -21,6 +21,7 @@ , local -- * Re-exports , Algebra+, Effect , Has , run ) where
src/Control/Effect/State.hs view
@@ -24,6 +24,7 @@ , modifyLazy -- * Re-exports , Algebra+, Effect , Has , run ) where
src/Control/Effect/Throw.hs view
@@ -13,6 +13,7 @@ , throwError -- * Re-exports , Algebra+, Effect , Has , run ) where
src/Control/Effect/Trace.hs view
@@ -17,6 +17,7 @@ , trace -- * Re-exports , Algebra+, Effect , Has , run ) where
src/Control/Effect/Writer.hs view
@@ -23,6 +23,7 @@ , censor -- * Re-exports , Algebra+, Effect , Has , run ) where