bluefin 0.0.17.1 → 0.7.0.0
raw patch · 37 files changed
Files
- CHANGELOG.md +107/−0
- bluefin.cabal +19/−2
- src/Bluefin.hs +140/−156
- src/Bluefin/Capability.hs +40/−0
- src/Bluefin/Capability/Ask.hs +21/−0
- src/Bluefin/Capability/AskCapability.hs +29/−0
- src/Bluefin/Capability/Await.hs +22/−0
- src/Bluefin/Capability/JumpTo.hs +18/−0
- src/Bluefin/Capability/Modify.hs +17/−0
- src/Bluefin/Capability/Request.hs +89/−0
- src/Bluefin/Capability/ReturnEarly.hs +17/−0
- src/Bluefin/Capability/Tell.hs +19/−0
- src/Bluefin/Capability/Throw.hs +16/−0
- src/Bluefin/Capability/Yield.hs +35/−0
- src/Bluefin/CloneableHandle.hs +91/−0
- src/Bluefin/Compound.hs +150/−123
- src/Bluefin/Consume.hs +7/−0
- src/Bluefin/Coroutine.hs +18/−1
- src/Bluefin/DslBuilder.hs +341/−0
- src/Bluefin/DslBuilderEff.hs +10/−0
- src/Bluefin/EarlyReturn.hs +5/−0
- src/Bluefin/Eff.hs +11/−2
- src/Bluefin/Exception.hs +5/−0
- src/Bluefin/Exception/GeneralBracket.hs +19/−0
- src/Bluefin/GadtEffect.hs +251/−0
- src/Bluefin/HandleReader.hs +29/−8
- src/Bluefin/IO.hs +9/−2
- src/Bluefin/Jump.hs +4/−0
- src/Bluefin/Pipes.hs +15/−9
- src/Bluefin/Pipes/Prelude.hs +17/−11
- src/Bluefin/Prim.hs +44/−0
- src/Bluefin/Reader.hs +7/−1
- src/Bluefin/State.hs +5/−0
- src/Bluefin/StateSource.hs +1/−1
- src/Bluefin/Stream.hs +9/−1
- src/Bluefin/System/IO.hs +0/−2
- src/Bluefin/Writer.hs +5/−0
CHANGELOG.md view
@@ -1,3 +1,110 @@+# 0.7.0.0++* Fix `Reader` bug that caused incorrect scoping in+ `awaitYield`/`connectRequests`/`streamConsume`/`connectCoroutines`++ <https://github.com/tomjaguarpaw/bluefin/issues/98>++# 0.6.0.0++* Changed type of `runEff` to match `runEff_`++# 0.5.100.0++* Covert to "capability" nomenclature++ See module "Bluefin.Capability" for a guide to the new naming. Users+ should convert to the new modules, since the old ones will be+ deprecated in the future. This is indicated by a comment in the+ documentation for each module that will undergo deprecation.++ * Add new "Capability" modules++ * Use "capability" terminology throughout documentation++# 0.5.1.0++* Add `<:` type synonym for `:>`++ It is suggested you switch your uses of `:>` to `<:` because it is a+ "subset" like constraint and the latter looks more like a subset+ symbol. The former may be deprecated and then removed in future+ versions.++# 0.5.0.0++* Fix dodgy `Bluefin.Primitive.primitive` implementation++# 0.4.3.0++* Add `Bluefin.GadtEffect`++* Add `Bluefin.HandleReader.asksHandle`++* Restrict type parameter of `PrimStateEff` to `Effects`++ This is technically a breaking change, but we did not enforce a+ major version bump for it.++# 0.4.2.0++* Add `Bluefin.DslBuilderEff`++* Add `Bluefin.Prim`++# 0.4.1.0++* Depend on `bluefin-internal >= 0.4.1.0` to pick up `MonadFix`+ instance for `Eff`++# 0.4.0.1++* Documentation only++# 0.4.0.0++* Move `mapHandle` out of class `Handle` and remove `handleMapHandle`.+ See `Bluefin.Compound` for instructions about migrating.++# 0.2.7.0++* Add `Bluefin.Compound.handleOneWayCoercible`++* Add `Bluefin.Compound.oneWayCoercibleTrustMe`++* Add `OneWayCoercible` instances for Generic types++# 0.2.6.0++* Add `Bluefin.Exception.GeneralBracket`, thanks to Shea Levy++# 0.2.5.0++* Add `finally`++# 0.2.4.0++* Add `Bluefin.CloneHandle` and `Bluefin.IO.withEffToIOCloneHandle`++# 0.2.3.0++* Add `Bluefin.DslBuilder`++## 0.2.2.0++* `Bluefin.Compound`: add `OneWayCoercible`, `OneWayCoercibleHandle`,+ `gOneWayCoercible` and re-export `Generic`++## 0.2.1.0++* `Bluefin.Compound`: Add `handleImpl`, `HandleD` and+ `handleMapHandle`++## 0.2.0.0++* Transitive version bump because of choice of different incoherent+ instance for `:>` for better type inference.+ ## 0.0.17.1 * Documentation only, thanks to @ShilohAlleyne
bluefin.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: bluefin-version: 0.0.17.1+version: 0.7.0.0 license: MIT license-file: LICENSE author: Tom Ellis@@ -21,17 +21,34 @@ NoImplicitPrelude exposed-modules: Bluefin,+ Bluefin.Capability,+ Bluefin.Capability.Ask,+ Bluefin.Capability.AskCapability,+ Bluefin.Capability.Await,+ Bluefin.Capability.JumpTo,+ Bluefin.Capability.Modify,+ Bluefin.Capability.Request,+ Bluefin.Capability.ReturnEarly,+ Bluefin.Capability.Tell,+ Bluefin.Capability.Throw,+ Bluefin.Capability.Yield, Bluefin.Compound, Bluefin.Consume, Bluefin.Coroutine,+ Bluefin.CloneableHandle,+ Bluefin.DslBuilder,+ Bluefin.DslBuilderEff, Bluefin.EarlyReturn, Bluefin.Eff, Bluefin.Exception,+ Bluefin.Exception.GeneralBracket,+ Bluefin.GadtEffect, Bluefin.HandleReader, Bluefin.IO, Bluefin.Jump, Bluefin.Pipes, Bluefin.Pipes.Prelude,+ Bluefin.Prim, Bluefin.Reader, Bluefin.State, Bluefin.StateSource,@@ -39,6 +56,6 @@ Bluefin.System.IO, Bluefin.Writer, build-depends:- bluefin-internal >= 0.1.1.0 && < 0.2+ bluefin-internal >= 0.7 && < 0.8 hs-source-dirs: src default-language: Haskell2010
src/Bluefin.hs view
@@ -4,16 +4,16 @@ -- | Bluefin is an effect system which allows you to freely mix a -- variety of effects, including --- -- * "Bluefin.EarlyReturn", for early return- -- * "Bluefin.Exception", for exceptions+ -- * "Bluefin.Capability.ReturnEarly", for early return+ -- * "Bluefin.Capability.Throw", for exceptions -- * "Bluefin.IO", for I/O- -- * "Bluefin.State", for mutable state- -- * "Bluefin.Stream", for streams+ -- * "Bluefin.Capability.Modify", for mutable state+ -- * "Bluefin.Capability.Yield", for streams -- -- and to create your own effects in terms of existing ones -- ("Bluefin.Compound"). -- Bluefin effects are accessed explicitly through- -- value-level handles.+ -- value-level capabilities. -- * Why even use an effect system? @@ -107,17 +107,18 @@ -- question: if @let@ bindings don't interact with effects, -- because we can inline them freely, then how /can/ we perform -- effects in Haskell, and maintain control over the order in- -- which various operations occur? For a hour-long answer,+ -- which various operations occur? For an hour-long answer, -- concluding with an explanation of the development of effect -- systems, you can watch "[A History of Effect- -- systems](https://www.youtube.com/watch?v=RsTuy1jXQ6Y)" by Tom+ -- systems](https://www.youtube.com/watch?v=RsTuy1jXQ6Y)" by Bluefin author Tom -- Ellis (recorded at Zurihac 2025). --- -- The short answer is: 'Control.Monad.Monad's. @Monad@ is a+ -- The short answer is: t'Control.Monad.Monad's. @Monad@ is a -- general interface that permits ordering of operations. -- Instances of @Monad@ from early in the development of Haskell- -- include 'Prelude.IO', 'Control.Monad.Trans.State.State',- -- 'Prelude.Either' and 'Control.Monad.Trans.State.Writer', all of+ -- include t'Prelude.IO',+ -- t'Control.Monad.Trans.State.Strict.State', t'Prelude.Either'+ -- and t'Control.Monad.Trans.Writer.CPS.Writer', all of -- which are still in use today. For example, to manipulate -- mutable state we can't use @let@ bindings in the following way: --@@ -139,7 +140,9 @@ -- -- which is not what we want at all: the final value would just be -- @"Initial value"@. An approach that /does/ work is to simulate- -- mutable state using an ad hoc "state passing" pattern:+ -- mutable state using an ad hoc "state passing" pattern. Here+ -- the variables @s1@ and @s2@ represent different values of the+ -- same state at different parts of program execution: -- -- @ -- let s1 = "Initial value"@@ -148,7 +151,7 @@ -- in "Final value: " ++ v -- @ --- -- Moreover, we can define a 'Control.Monad.Trans.State.State'+ -- Moreover, we can define a t'Control.Monad.Trans.State.Strict.State' -- monad which casts the ad hoc state passing pattern as a general -- pattern known as "monad": --@@ -186,10 +189,10 @@ -- [@transformers@](https://hackage.haskell.org/package/transformers) -- and [@mtl@](https://hackage.haskell.org/package/mtl) libraries. -- The transformer extensions of @State@ and @Either@ are- -- 'Control.Monad.Trans.State.StateT' and- -- 'Control.Monad.Trans.State.ExceptT', and the @Mt@ extensions- -- are 'Control.Monad.State.MonadState' and- -- 'Control.Monad.Error.MonadError'. We won't go into more detail+ -- t'Control.Monad.Trans.State.Strict.StateT' and+ -- t'Control.Monad.Trans.ExceptT', and the @mtl@ extensions+ -- are t'Control.Monad.State.Strict.MonadState' and+ -- t'Control.Monad.Except.MonadError'. We won't go into more detail -- here because this documentation isn't a transformers or MTL -- tutorial, but here is an example of an MTL-style function that -- uses those two effects, and no others:@@ -247,7 +250,7 @@ -- @ -- ** \"Synthetic\" effect systems provide fine-grained effects and encapsulation- --+ -- | The approach of building effects from smaller pieces by -- combining algebraic data types, and then interpreting those -- pieces to "handle" some of the effects can be called the@@ -264,7 +267,7 @@ -- possible effects by handling an effect. -- *** The downside of synthetic effects- --+ -- | Unfortunately, synthetic effects have two notable downsides: -- firstly they have unpredictable performance, and secondly they -- make it hard to achieve resource safety. The first point –@@ -304,7 +307,7 @@ -- Brackets](https://academy.fpblock.com/blog/2017/06/tale-of-two-brackets/)". -- ** @IO@-wrapper effect systems- --+ -- | -- -- An alternative to synthetic effects that does allows@@ -396,7 +399,7 @@ -- /-- > exampleBluefin/ -- /-- 55/ -- exampleBluefin :: Int- -- exampleBluefin = runPureEff $ evalState 0 $ \\st -> do+ -- exampleBluefin = runPureEff $ evalModify 0 $ \\st -> do -- for_ [1..10] $ \\i -> do -- modify st (+ i) -- get st@@ -406,7 +409,7 @@ -- /-- > exampleEffectful/ -- /-- 55/ -- exampleEffectful :: Int- -- exampleEffectful = runPureEff $ evalState 0 $ do+ -- exampleEffectful = runPureEff $ evalModify 0 $ do -- for_ [1..10] $ \\i -> do -- modify (+ i) -- get@@ -419,7 +422,7 @@ -- If we get the best of both worlds with analytic effect systems, -- is there a downside? Yes, the downside is that analytic effect -- systems do not support multishot continuations, like- -- 'Control.Monad.Logic.LogicT' implements. Here's an example of+ -- t'Control.Monad.Logic.LogicT' implements. Here's an example of -- using multishot continuations to calculate all sums of paths -- from root to leaf in a tree. In the @Branch@ alternative, -- @allSums t@ is a "multishot" continuation because it is run@@ -455,97 +458,78 @@ -- * A Comparison of effect systems at a glance - -- ** Mixing effects+ -- ** IO -- |- -- - ✅ __IO__: I\/O, state via @IORef@, exceptions via @throw@/@catch@- -- - ❌ __ST__: State only- -- - ✅ __MTL__\/__fused-effects__\/__Polysemy__- -- - ✅ __Bluefin__\/__effectful__+ -- - ✅ __Mixing effects__: I\/O, state via @IORef@, exceptions via @throw@/@catch@+ -- - ❌ __Fine-grained effects__: No distinction between different effects (state, exceptions, I/O, etc.)+ -- - ❌ __Encapsulation__: Can handle exceptions, but doing so is not+ -- reflected in the type+ -- - ✅ __Resource safety__: Operations can be bracketed (see+ -- @Control.Exception.'Control.Exception.bracket'@)+ -- - ✅ __Predictable performance__+ -- - ❌ __Multishot continuations__ - -- ** Fine-grained Effects+ -- ** ST -- |- -- - ❌ __IO__: No distinction between different effects (state, exceptions, I/O, etc.)- -- - ✅ __ST__: But state only- -- - ✅ __MTL__\/__fused-effects__\/__Polysemy__: Individual effects are represented at the type level- -- - ✅ __Bluefin__\/__effectful__: Individual effects are represented at the type level+ -- - ❌ __Mixing effects__: State only+ -- - ✅ __Fine-grained effects__: But state only+ -- - ✅ __Encapsulation__: State effects handled by @runST@ are not present+ -- in the operation's type signature+ -- - ❌ __Resource safety__: State only+ -- - ✅ __Predictable performance__+ -- - ❌ __Multishot continuations__ - -- ** Encapsulation+ -- ** MTL\/fused-effects\/Polysemy -- |- --- -- - ❌ __IO__: Can handle exceptions, but doing so is not- -- reflected in the type- --- -- - ❌ __ST__: State only- --- -- - ✅ __MTL__\/__fused-effects__\/__Polysemy__: Exceptions,- -- state and other effects handled in the body of an operation+ -- - ✅ __Mixing effectns__+ -- - ✅ __Fine-grained effects__: Individual effects are represented at the type level+ -- - ✅ __Encapsulation__: Exceptions, state and other effects handled in the body of an operation -- are not present in the operation's type signature- --- -- - ✅ __Bluefin__\/__effectful__: Exceptions, state and other- -- effects handled in the body of an operation are not present- -- in the operation's type signature+ -- - ❌ __Resource safety__: Difficult to achieve resource safety for arbitrary effects+ -- - ❌ __Predictable performance__: Good performance depends critically on GHC optimization+ -- - ✅ __Multishot continuations__ - -- ** Resource Safety+ -- ** Bluefin\/effectful -- |- -- - ✅ __IO__: Operations can be bracketed (see- -- @Control.Exception.'Control.Exception.bracket'@)- --- -- - ❌ __ST__: State only- --- -- - ❌ __MTL__\/__fused-effects__\/__Polysemy__: Difficult to- -- achieve resource safety for arbitrary effects- --- -- - ✅ __Bluefin__\/__effectful__: Operations can be bracketed+ -- - ✅ __Mixing effects__+ -- - ✅ __Fine-grained effects__: Individual effects are represented at the type level+ -- - ✅ __Encapsulatio__: Exceptions, state and other+ -- effects handled in the body of an operation are not present+ -- in the operation's type signature+ -- - ✅ __Resource safety__: Operations can be bracketed -- (see e.g. @Bluefin.Eff.'Bluefin.Eff.bracket'@) because these -- effect systems wrap @IO@-- -- ** Predictable Performance-- -- |- -- - ✅ __IO__: Predictable performance- -- - ✅ __ST__: Predictable performance- --- -- - ❌ __MTL__\/__fused-effects__\/__Polysemy__: Good performance- -- depends critically on GHC optimization- --- -- - ✅ __Bluefin__\/__effectful__: Predictable performance+ -- - ✅ __Predictable performance__: Predictable performance -- because these effect systems wrap @IO@-- -- ** Multishot continuations-- -- |- -- - ❌ __IO__- -- - ❌ __ST__- -- - ✅ __MTL__\/__fused-effects__\/__Polysemy__- -- - ❌ __Bluefin__\/__effectful__+ -- - ❌ __Multishot continuations__ -- * Introduction to Bluefin -- | Bluefin is a Haskell effect system with a new style of API. -- It is distinct from prior effect systems because effects are- -- accessed explicitly through value-level handles which occur as- -- arguments to effectful operations. Handles (such as- -- 'Bluefin.State.State' handles, which allow access to mutable+ -- accessed explicitly through value-level capabilities which occur as+ -- arguments to effectful operations. Capabilities (such as+ -- 'Bluefin.Capability.Modify.Modify' capabilities, which allow access to mutable -- state) are introduced by handlers (such as- -- 'Bluefin.State.evalState', which sets the initial state).- -- Here's an example where a mutable state effect handle, @sn@, is- -- introduced by its handler, 'Bluefin.State.evalState'.+ -- 'Bluefin.Capability.Modify.evalModify', which sets the initial state).+ -- Here's an example where a mutable state effect capability, @sn@, is+ -- introduced by its handler, 'Bluefin.Capability.Modify.evalModify'. -- -- @ -- -- If @n < 10@ then add 10 to it, otherwise -- -- return it unchanged -- example1 :: Int -> Int -- example1 n = 'Bluefin.Eff.runPureEff' $- -- -- Create a new state handle, sn, and+ -- -- Create a new modify capability, sn, and -- -- initialize the value of the state to n- -- 'Bluefin.State.evalState' n $ \\sn -> do- -- n' <- 'Bluefin.State.get' sn+ -- 'Bluefin.Capability.Modify.evalModify' n $ \\sn -> do+ -- n' <- 'Bluefin.Capability.Modify.get' sn -- when (n' < 10) $- -- 'Bluefin.State.modify' sn (+ 10)+ -- 'Bluefin.Capability.Modify.modify' sn (+ 10) -- get sn -- @ --@@ -556,12 +540,12 @@ -- 12 -- @ --- -- The handle @sn@ is used in much the same way as an+ -- The capability @sn@ is used in much the same way as an -- 'Data.STRef.STRef' or 'Data.IORef.IORef'. -- ** Multiple effects of the same type - -- | A benefit of value-level effect handles is that it's simple+ -- | A benefit of value-level effect capabilities is that it's simple -- to have multiple effects of the same type in scope at the same -- time. It is simple to disambiguate them, because they are -- distinct values! By contrast, existing effect systems require@@ -576,14 +560,14 @@ -- -- to the smaller -- example2 :: (Int, Int) -> (Int, Int) -- example2 (m, n) = 'Bluefin.Eff.runPureEff' $- -- 'Bluefin.State.evalState' m $ \\sm -> do- -- evalState n $ \\sn -> do+ -- 'Bluefin.Capability.Modify.evalModify' m $ \\sm -> do+ -- evalModify n $ \\sn -> do -- do- -- n' <- 'Bluefin.State.get' sn+ -- n' <- 'Bluefin.Capability.Modify.get' sn -- m' <- get sm -- -- if n' < m'- -- then 'Bluefin.State.modify' sn (+ 10)+ -- then 'Bluefin.Capability.Modify.modify' sn (+ 10) -- else modify sm (+ 10) -- -- n' <- get sn@@ -599,11 +583,11 @@ -- (30, 13) -- @ - -- ** Exception handles+ -- ** Exception capabilities -- | Bluefin exceptions are accessed through- -- 'Bluefin.Exception.Exception' handles. An @Exception@ handle- -- is introduced by a handler, such as 'Bluefin.Exception.try',+ -- 'Bluefin.Capability.Throw.Throw' capabilities. A @Throw@ capability+ -- is introduced by a handler, such as 'Bluefin.Capability.Throw.try', -- and that handler is where the exception, if thrown, will be -- handled. This arrangement differs from normal Haskell -- exceptions in two ways. Firstly, every Bluefin exception will@@ -612,26 +596,26 @@ -- only one place – normal Haskell exceptions can be handled in a -- variety of places, and the closest handler of matching type on -- the stack will be the one that will be chosen upon- -- 'Control.Exception.throw'.+ -- @Control.Exception.'Control.Exception.throw'@. -- -- @example3@ shows how to use Bluefin to calculate the sum of -- numbers from 1 to @n@, but stop if the sum becomes bigger than- -- 20. The exception handle, @ex@, which has type @Exception+ -- 20. The throw capability, @ex@, which has type @Throw -- String e@, cannot escape the scope of its handler, @try@. If -- thrown it will be handled at that @try@, and nowhere else. -- -- @ -- example3 :: Int -> Either String Int -- example3 n = 'Bluefin.Eff.runPureEff' $- -- 'Bluefin.Exception.try' $ \\ex -> do- -- 'Bluefin.State.evalState' 0 $ \\total -> do+ -- 'Bluefin.Capability.Throw.try' $ \\ex -> do+ -- 'Bluefin.Capability.Modify.evalModify' 0 $ \\total -> do -- for_ [1..n] $ \\i -> do- -- soFar <- 'Bluefin.State.get' total+ -- soFar <- 'Bluefin.Capability.Modify.get' total -- when (soFar > 20) $ do- -- 'Bluefin.Exception.throw' ex ("Became too big: " ++ show soFar)- -- 'Bluefin.State.put' total (soFar + i)+ -- 'Bluefin.Capability.Throw.throw' ex ("Became too big: " ++ show soFar)+ -- 'Bluefin.Capability.Modify.put' total (soFar + i) --- -- 'Bluefin.State.get' total+ -- 'Bluefin.Capability.Modify.get' total -- @ -- -- @@@ -644,68 +628,68 @@ -- ** Effect scoping -- | Bluefin's use of the type system is very similar to- -- "Control.Monad.ST": it ensures that a handle can never escape+ -- "Control.Monad.ST": it ensures that a capability can never escape -- the scope of its handler. That is, once the handler has- -- finished running there is no way you can use the handle+ -- finished running there is no way you can use the capability -- anymore. For an example of a correctly-scoped function see- -- @correctlyScoped@ below. It uses Bluefin’s @State@ handle to+ -- @correctlyScoped@ below. It uses Bluefin’s @Modify@ capability to -- compute the sum of the numbers 1 to 10, before multiplying the- -- result by 20. In @correctlyScoped@ the @State@ handle is scoped- -- to its handler, @evalState@, and everything works as expected:+ -- result by 20. In @correctlyScoped@ the @Modify@ capability is scoped+ -- to its handler, @evalModify@, and everything works as expected: -- -- @ -- -- /Result: 1100/ -- correctlyScoped :: Eff es Integer -- correctlyScoped = do -- -- /Initial state 0/- -- r \<- 'Bluefin.State.evalState' 0 $ \\st -> do- -- -- The 'Bluefin.State.State' handle "st" is scoped to the- -- -- handler that introduced it, evalState,+ -- r \<- 'Bluefin.Capability.Modify.evalModify' 0 $ \\st -> do+ -- -- The 'Bluefin.Capability.Modify.Modify' handle "st" is scoped to the+ -- -- handler that introduced it, evalModify, -- -- and therefore it can only be used within -- -- this do block. -- -- -- /Add up the numbers 1 to 10/ -- for_ [1..10] $ \\i -> do- -- 'Bluefin.State.modify' st (+ i)+ -- 'Bluefin.Capability.Modify.modify' st (+ i) -- -- -- /Get the result/- -- 'Bluefin.State.get' st+ -- 'Bluefin.Capability.Modify.get' st -- -- pure (r * 20) -- @ -- -- Now let's look at an incorrectly-scoped example,- -- @incorrectlyScoped@. It attempts to pass the state handle @st@- -- out of the scope of @evalState@:+ -- @incorrectlyScoped@. It attempts to pass the modify capability @st@+ -- out of the scope of @evalModify@: -- -- @ -- incorrectlyScoped :: Eff es Integer -- incorrectlyScoped = do -- -- /Initial state 0/- -- (total, st) \<- 'Bluefin.State.evalState' 0 $ \\st -> do+ -- (total, st) \<- 'Bluefin.Capability.Modify.evalModify' 0 $ \\st -> do -- -- /Add up the numbers 1 to 10/ -- for_ [1..10] $ \\i -> do- -- 'Bluefin.State.modify' st (+ i)+ -- 'Bluefin.Capability.Modify.modify' st (+ i) -- -- -- /Get the result/- -- r <- 'Bluefin.State.get' st+ -- r <- 'Bluefin.Capability.Modify.get' st -- -- -- /Pass out the result, and try to pass the/- -- -- /'Bluefin.State.State' handle outside its scope, i.e. this/- -- -- /do block introduced by evalState/+ -- -- /'Bluefin.Capability.Modify.Modify' capability outside its scope, i.e. this/+ -- -- /do block introduced by evalModify/ -- pure (r, st) -- -- modify st (* 20) -- get st -- @ --- -- The type system prevents us from passing the @State@ handle out+ -- The type system prevents us from passing the @Modify@ capability out -- of its scope, giving this error message: -- -- @ -- • Couldn't match type ‘e0’ with ‘e’- -- Expected: (Integer, State Integer e0)- -- Actual: (Integer, State Integer e)+ -- Expected: (Integer, Modify Integer e0)+ -- Actual: (Integer, Modify Integer e) -- because type variable ‘e’ would escape its scope -- @ @@ -715,19 +699,19 @@ -- pattern which looks like -- -- @- -- (e1 :> es, ...) -> \<Handle\> e1 -> ... -> Eff es r+ -- (e1 \<: es, ...) -> \<Capability\> e1 -> ... -> Eff es r -- @ --- -- Here @\<Handle\>@ could be, for example, @State Int@,- -- @Exception String@ or @IOE@. Consider the function below,+ -- Here @\<Capability\>@ could be, for example, @Modify Int@,+ -- @Throw String@ or @IOE@. Consider the function below, -- @incrementReadLine@. It reads integers from standard input, -- accumulates them into a state; it returns when it reads the -- input integer @0@ and it throws an exception if it encounters -- an input line it cannot parse. --- -- Firstly, let's look at the arguments, which are all handles to- -- Bluefin effects. There is a state handle, an exception handle,- -- and an IO handle, which allow modification of an @Int@ state,+ -- Firstly, let's look at the arguments, which are all capabilities.+ -- There is a modify capability, a throw capability,+ -- and an IO capability, which allow modification of an @Int@ state, -- throwing a @String@ exception, and performing @IO@ operations -- respectively. They are each tagged with a different effect -- type, @e1@, @e2@ and @e3@ respectively, which are always kept@@ -742,10 +726,10 @@ -- Finally, let's look at the constraints. They are what tie -- together the effect tags of the arguments to the effect tag of -- the result. For every argument effect tag @en@ we have a- -- constraint @en :> es@. That tells us the that effect handle+ -- constraint @en \<: es@. That tells us the that capability -- with tag @en@ is allowed to be used within the effectful- -- computation. If we didn't have the @e1 :> es@ constraint, for- -- example, that would tell us that the @State Int e1@ isn't+ -- computation. If the @e1 \<: es@ constraint, for+ -- example, were not required that would tell us that the @Modify Int e1@ isn't -- actually used anywhere in the computation. -- -- GHC and editor tools like HLS do a good job of inferring these@@ -753,9 +737,9 @@ -- -- @ -- incrementReadLine ::- -- (e1 :> es, e2 :> es, e3 :> es) =>- -- State Int e1 ->- -- Exception String e2 ->+ -- (e1 \<: es, e2 \<: es, e3 \<: es) =>+ -- Modify Int e1 ->+ -- Throw String e2 -> -- IOE e3 -> -- Eff es () -- incrementReadLine state exception io = do@@ -763,39 +747,39 @@ -- line <- 'Bluefin.IO.effIO' io getLine -- i <- case 'Text.Read.readMaybe' line of -- Nothing ->- -- 'Bluefin.Exception.throw' exception ("Couldn't read: " ++ line)+ -- 'Bluefin.Capability.Throw.throw' exception ("Couldn't read: " ++ line) -- Just i -> -- pure i -- -- when (i == 0) $ -- 'Bluefin.Jump.jumpTo' break --- -- 'Bluefin.State.modify' state (+ i)+ -- 'Bluefin.Capability.Modify.modify' state (+ i) -- @ -- -- Now let's look at how we can run such a function. Each effect -- must be handled by a corresponding handler, for example- -- 'Bluefin.State.runState' for the state effect,- -- 'Bluefin.Exception.try' for the exception effect and- -- 'Bluefin.Eff.runEff_' for the @IO@ effect. The type signatures+ -- 'Bluefin.Capability.Modify.runModify' for the state effect,+ -- 'Bluefin.Capability.Throw.try' for the exception effect and+ -- 'Bluefin.Eff.runEff' for the @IO@ effect. The type signatures -- of handlers also follow a common pattern, which looks like -- -- @ -- (forall e. \<Handle\> e -> Eff (e :& es) a) -> Eff es r -- @ --- -- This means that the effect @e@, corresponding to the handle- -- @\<Handle\> e@, has been handled and removed from the set of+ -- This means that the effect tag @e@, corresponding to the capability+ -- @\<Capability\> e@, has been handled and removed from the set of -- remaining effects, @es@. (The signatures for- -- 'Bluefin.Eff.runEff_' and 'Bluefin.Eff.runPureEff' are slightly+ -- 'Bluefin.Eff.runEff' and 'Bluefin.Eff.runPureEff' are slightly -- different because they remove @Eff@ itself.) Here, then, is -- how we can run @incrementReadLine@: -- -- @ -- runIncrementReadLine :: IO (Either String Int)- -- runIncrementReadLine = 'Bluefin.Eff.runEff_' $ \\io -> do- -- 'Bluefin.Exception.try' $ \\exception -> do- -- ((), r) \<- 'Bluefin.State.runState' 0 $ \\state -> do+ -- runIncrementReadLine = 'Bluefin.Eff.runEff' $ \\io -> do+ -- 'Bluefin.Capability.Throw.try' $ \\exception -> do+ -- ((), r) \<- 'Bluefin.Capability.Modify.runModify' 0 $ \\state -> do -- incrementReadLine state exception io -- pure r --@@ -826,7 +810,7 @@ -- ** @effectful@ -- | The major difference between Bluefin and @effectful@ is that in- -- Bluefin effects are represented as value-level handles whereas+ -- Bluefin effects are represented as value-level capabilities whereas -- in @effectful@ they are represented only at the type level. -- @effectful@ could be described as "a well-typed implementation of -- the @ReaderT@ @IO@ pattern", and Bluefin could be described as@@ -852,22 +836,22 @@ -- | Bluefin has a similar implementation style to @effectful@. -- t'Bluefin.Eff.Eff' is an opaque wrapper around 'IO',- -- t'Bluefin.State.State' is an opaque wrapper around- -- 'Data.IORef.IORef', and 'Bluefin.Exception.throw' throws an- -- actual @IO@ exception. t'Bluefin.Coroutine.Coroutine' is+ -- t'Bluefin.Capability.Modify.Modify' is an opaque wrapper around+ -- 'Data.IORef.IORef', and 'Bluefin.Capability.Throw.throw' throws an+ -- actual @IO@ exception. t'Bluefin.Capability.Request.Request' is -- implemented simply as a function. -- -- @ -- newtype t'Bluefin.Eff.Eff' (es :: 'Bluefin.Eff.Effects') a = 'Bluefin.Internal.UnsafeMkEff' (IO a)- -- newtype t'Bluefin.State.State' s (st :: Effects) = 'Bluefin.Internal.UnsafeMkState' (IORef s)- -- newtype t'Bluefin.Coroutine.Coroutine' a b (s :: Effects) = 'Bluefin.Internal.UnsafeMkCoroutine' (a -> IO b)+ -- newtype t'Bluefin.Capability.Modify.Modify' s (st :: Effects) = 'Bluefin.Internal.UnsafeMkState' (IORef s)+ -- newtype t'Bluefin.Capability.Request.Request' a b (s :: Effects) = 'Bluefin.Internal.UnsafeMkCoroutine' (a -> IO b) -- @ -- -- The type parameters of kind t'Bluefin.Eff.Effects' are phantom -- type parameters which track which effects can be used in an -- operation. Bluefin uses them to ensure that effects cannot -- escape the scope of their handler, in the same way that the- -- type parameter to the 'Control.Monad.ST.ST' monad ensures that+ -- type parameter to the t'Control.Monad.ST.ST' monad ensures that -- mutable state references cannot escape -- 'Control.Monad.ST.runST'. When the type system indicates that -- there are no unhandled effects it is safe to run the underlying@@ -895,16 +879,16 @@ -- @ -- countPositivesNegatives :: [Int] -> String -- countPositivesNegatives is = 'Bluefin.Eff.runPureEff' $- -- 'Bluefin.State.evalState' (0 :: Int) $ \\positives -> do- -- r \<- 'Bluefin.Exception.try' $ \\ex ->- -- evalState (0 :: Int) $ \\negatives -> do+ -- 'Bluefin.Capability.Modify.evalModify' (0 :: Int) $ \\positives -> do+ -- r \<- 'Bluefin.Capability.Throw.try' $ \\ex ->+ -- evalModify (0 :: Int) $ \\negatives -> do -- for_ is $ \\i -> do -- case compare i 0 of- -- GT -> 'Bluefin.State.modify' positives (+ 1)+ -- GT -> 'Bluefin.Capability.Modify.modify' positives (+ 1) -- EQ -> throw ex () -- LT -> modify negatives (+ 1) --- -- p <- 'Bluefin.State.get' positives+ -- p <- 'Bluefin.Capability.Modify.get' positives -- n <- get negatives -- -- pure $
+ src/Bluefin/Capability.hs view
@@ -0,0 +1,40 @@+module Bluefin.Capability+ ( -- * Historical commentary++ -- | Bluefin is in a transitionary phase moving away from the old+ -- terminology of "handle" and naming handles/effects based on+ -- MTL\/transformers style names+ -- (e.g. @Exception@\/@Reader@\/@Stream@) and moving towards+ -- calling these things "capabilities" and naming them after their+ -- main operation (e.g. @Throw@\/@Ask@\/@Yield@). You are+ -- encouraged to use the API beneath @Bluefin.Capability@ because+ -- that will be the supported API in the future.+ --+ -- You are encouraged to change your usage of the old modules on+ -- the left to the new modules on the right:+ --+ -- +------------------------+------------------------------------------++ -- | Old | New |+ -- +========================+==========================================++ -- | "Bluefin.Reader" | "Bluefin.Capability.Ask" |+ -- +------------------------+------------------------------------------++ -- | "Bluefin.HandleReader" | "Bluefin.Capability.AskCapability" |+ -- +------------------------+------------------------------------------++ -- | "Bluefin.Consume" | "Bluefin.Capability.Await" |+ -- +------------------------+------------------------------------------++ -- | "Bluefin.Jump" | "Bluefin.Capability.JumpTo" |+ -- +------------------------+------------------------------------------++ -- | "Bluefin.State" | "Bluefin.Capability.Modify" |+ -- +------------------------+------------------------------------------++ -- | "Bluefin.Coroutine" | "Bluefin.Capability.Request" |+ -- +------------------------+------------------------------------------++ -- | "Bluefin.EarlyReturn" | "Bluefin.Capability.ReturnEarly" |+ -- +------------------------+------------------------------------------++ -- | "Bluefin.Writer" | "Bluefin.Capability.Tell" |+ -- +------------------------+------------------------------------------++ -- | "Bluefin.Exception" | "Bluefin.Capability.Throw" |+ -- +------------------------+------------------------------------------++ -- | "Bluefin.Stream" | "Bluefin.Capability.Yield" |+ -- +------------------------+------------------------------------------++ )+where
+ src/Bluefin/Capability/Ask.hs view
@@ -0,0 +1,21 @@+module Bluefin.Capability.Ask+ ( -- | 'Ask' is Bluefin's version of the+ -- "Control.Monad.Trans.Reader" monad. 'local' allows you to+ -- locally override the @ask@ed value in a well-scoped way. The+ -- original value will be restored when you exit the @local@ block+ -- regardless of whether the exit was normal or via an exception.++ -- * Capability+ Ask,++ -- * Handlers+ runAsk,++ -- * Effectful operations+ ask,+ asks,+ local,+ )+where++import Bluefin.Internal
+ src/Bluefin/Capability/AskCapability.hs view
@@ -0,0 +1,29 @@+-- | 'AskCapability' is like t'Bluefin.Capability.Ask.Ask', generalized to+-- work for arbitrary t'Bluefin.Compound.Handle's. 'localCapability'+-- locally overrides the value of a capability in a well-scoped way. The+-- original capability will be restored when you exit the @localCapability@+-- block regardless of whether the exit was normal or via an+-- exception.+--+-- @AskCapability@ supports functionality similiar to @effectful@'s+-- [@interpose@](https://hackage.haskell.org/package/effectful-core/docs/Effectful-Dispatch-Dynamic.html#v:interpose)+-- and @polysemy@'s+-- [@intercept@](https://hackage.haskell.org/package/polysemy/docs/Polysemy.html#v:intercept),+-- that is, locally augmenting an effect with new behaviors. If you+-- want to do the same in Bluefin you may want to start with+-- @Bluefin.GadtEffect.'Bluefin.GadtEffect.interpose`@.+module Bluefin.Capability.AskCapability+ ( -- * Handle+ AskCapability,++ -- * Handlers+ runAskCapability,++ -- * Effectful operations+ askCapability,+ asksCapability,+ localCapability,+ )+where++import Bluefin.Internal
+ src/Bluefin/Capability/Await.hs view
@@ -0,0 +1,22 @@+module Bluefin.Capability.Await+ ( -- | 'Await' allows you to await values during the execution of+ -- a Bluefin operation. It provides similar functionality to+ -- @await@ from Conduit or Pipes.+ --+ -- For information about prompt finalization/resource safety when+ -- using Bluefin @Consume@s, see "Bluefin.Coroutine".++ -- * Capability+ Await,++ -- * Handlers+ eachAwait,+ awaitYield,++ -- * Effectful operations+ await,+ takeAwait,+ )+where++import Bluefin.Internal
+ src/Bluefin/Capability/JumpTo.hs view
@@ -0,0 +1,18 @@+module Bluefin.Capability.JumpTo+ ( -- | 'JumpTo' allows you to jump back to a previously-set location.+ -- A "jump" is equivalent to an untyped early return, or more+ -- precisely an early return of type @()@, which is itself an+ -- exception of type @()@.++ -- * Capability+ JumpTo,++ -- * Handlers+ withJumpTo,++ -- * Effectful operations+ jumpTo,+ )+where++import Bluefin.Internal
+ src/Bluefin/Capability/Modify.hs view
@@ -0,0 +1,17 @@+module Bluefin.Capability.Modify+ ( -- * Capability+ Modify,++ -- * Handlers+ evalModify,+ runModify,+ withModify,++ -- * Effectful operations+ get,+ put,+ modify,+ )+where++import Bluefin.Internal
+ src/Bluefin/Capability/Request.hs view
@@ -0,0 +1,89 @@+module Bluefin.Capability.Request+ ( -- | @Request@ allows to yield values and await the result. You+ -- might want to start with "Bluefin.Capability.Yield", which is+ -- the most common way to use @Request@s.++ -- ** Prompt finalization/resource safety++ -- | Bluefin+ -- t'Bluefin.Capability.Yield.Yield' \/ t'Bluefin.Capability.Await.Await' \/ t'Bluefin.Capability.Request.Request'+ -- computations have much better resource safety properties than+ -- Conduit and Pipes. You can use+ -- @Bluefin.Eff.'Bluefin.Eff.bracket'@ within a streaming+ -- computation and the acquired resource is guaranteed to be+ -- released and the end of the bracket, rather than at the end of+ -- the @ResourceT@ scope as it is the case in Conduit and Pipes.+ -- See the blog post [Bluefin streams finalize+ -- promptly](https://h2.jaguarpaw.co.uk/posts/bluefin-streams-finalize-promptly/)+ -- for more details.++ -- ** Running coroutines that communicate via @Request@s++ -- | Bluefin operations can be executed as coroutines using+ -- 'connectRequests' ([Wikipedia+ -- suggests](https://en.wikipedia.org/wiki/Coroutine#Definition_and_types)+ -- that such coroutines are "second-class stackful coroutines").+ -- Two coroutines run in this way communicate synchronously by+ -- using @Request@s to interact with a bi-directional+ -- channel. This means that such coroutines are often run+ -- exclusively for what they communicate via this channel, not+ -- for their return value.+ --+ -- @Request@s used in this way work a bit like UNIX pipes: there+ -- is a downstream consumer and an upstream generator. For every+ -- pair of such communicating coroutines there are two ends,+ -- represented with the capabilities @Request a b@ and @Request b+ -- a@. The first parameter to @Request@ is the type that can be+ -- /sent from/ that end, while the second parameter is the type+ -- that will subsequently be /received by/ that end. This explains+ -- the symmetry in the capabilities: what one end sends the other+ -- receives. The implication is that upstream and downstream+ -- exchange messages with each other at the same+ -- time. Additionally, there is a clear order of communication+ -- from the start (in Bluefin, communication is started by+ -- upstream).+ --+ -- 'request' is the only effectful operation required: a @Request+ -- a b@ capability that represents one end of a channel sends @a@s+ -- and receives a @b@s. For many use cases, upstream does not need+ -- to know anything from downstream (dually: downstream does not+ -- need to communicate anything to upstream) except that+ -- downstream is making a new request, so the capabilities that+ -- describe most channels are \"@Request a ()@\" and \"@Request ()+ -- a@\". Bluefin provides synonyms for these:+ -- @'Bluefin.Capability.Yield.Yield' a@ and+ -- @'Bluefin.Capability.Await.Await' a@, respectively. The+ -- specializations of @request@ for @Yield@ and @Await@ are called+ -- 'Bluefin.Capability.Yield.yield' and+ -- 'Bluefin.Capability.Await.await'. Coroutines that send data in+ -- only one direction like this can be created using 'awaitYield'.+ --+ -- Because the message exchange occurs synchronously, when yielding,+ -- the upstream will block until the downstream awaits. The converse+ -- is also true: when downstream awaits, it will block until upstream+ -- yields.+ --+ -- Any Bluefin effectful operation that takes a @Request@+ -- capability as an argument can be run as coroutine using+ -- 'connectRequests' by providing a second effectful operation+ -- as its counterpart on the other end of the channel.+ --+ -- For simple applications one may not need @connectRequests@ at+ -- all, because specific handlers are already provided by+ -- Bluefin. See the \"Handlers\" sections of the+ -- "Bluefin.Capability.Yield" and "Bluefin.Capability.Await"+ -- modules.++ -- * Capability+ Request,++ -- * Handlers+ forEach,+ connectRequests,++ -- * Effectful operations+ request,+ )+where++import Bluefin.Internal
+ src/Bluefin/Capability/ReturnEarly.hs view
@@ -0,0 +1,17 @@+module Bluefin.Capability.ReturnEarly+ ( -- | @Bluefin.ReturnEarly@ allows to define a block from which you can+ -- return early. Early return is implemented as an exception, and+ -- its API is just an alternate interface to exceptions.++ -- * Capability+ ReturnEarly,++ -- * Handlers+ withReturnEarly,++ -- * Effectful operations+ returnEarly,+ )+where++import Bluefin.Internal
+ src/Bluefin/Capability/Tell.hs view
@@ -0,0 +1,19 @@+module Bluefin.Capability.Tell+ ( -- | In most cases you'll probably prefer t'Bluefin.Capability.Yield.Yield'+ -- to @Tell@, but @Tell@ can still be useful in some cases,+ -- for example with @Data.Monoid.'Data.Monoid.Any'@ to determine+ -- whether an event ever occurred.++ -- * Capability+ Tell,++ -- * Handlers+ runTell,+ execTell,++ -- * Effectful operations+ tell,+ )+where++import Bluefin.Internal
+ src/Bluefin/Capability/Throw.hs view
@@ -0,0 +1,16 @@+module Bluefin.Capability.Throw+ ( -- * Capability+ Throw,++ -- * Handlers+ try,+ handle,+ catch,++ -- * Effectful operations+ throw,+ rethrowIO,+ )+where++import Bluefin.Internal
+ src/Bluefin/Capability/Yield.hs view
@@ -0,0 +1,35 @@+module Bluefin.Capability.Yield+ ( -- | 'Yield' allows you to yield values during the execution of a+ -- Bluefin operation. It provides similar functionality to+ -- Python's @yield@. The handler of the 'Yield' will either+ -- handle each element as soon as it is yielded (for example+ -- 'forEach') or gather all yielded elements into a list (for+ -- example 'yieldToList').+ --+ -- For information about prompt finalization/resource safety when+ -- using Bluefin @Yield@s, see "Bluefin.Capability.Request".++ -- * Capability+ Yield,++ -- * Handlers+ forEach,+ yieldToList,+ yieldToReverseList,+ withYieldToList,+ ignoreYield,+ enumerate,+ enumerateFrom,+ mapMaybe,+ catMaybes,+ awaitYield,++ -- * Effectful operations+ yield,+ inFoldable,+ cycleToYield,+ takeAwait,+ )+where++import Bluefin.Internal
+ src/Bluefin/CloneableHandle.hs view
@@ -0,0 +1,91 @@+-- | @Bluefin.CloneableHandle@ defines the 'CloneableHandle' class,+-- whose purpose is to support 'withEffToIOCloneHandle'.+module Bluefin.CloneableHandle+ ( -- | 'withEffToIOCloneHandle' is an @IO@ unlifting function that+ -- clones its handle each time it runs @Eff@ in @IO@. This is+ -- convenient when the unlifting function is being used to fork+ -- threads, since Bluefin state is not threadsafe. Be careful+ -- when you use it, because it can be used to throw away the+ -- effect tag on a Bluefin @Eff@ action due to this part of+ -- its type (here throwing away @e@):+ --+ -- @+ -- (forall e. IOE e -> h e -> Eff e r) -> IO r+ -- @+ --+ -- It is only safely used when you do not allow any effects to+ -- escape their scope, so we suggest that you use it sparingly to+ -- define reusable combinators which themselves are safe. For+ -- example, here is how you could write an equivalent of @async@'s+ -- @race@ primitive:+ --+ -- @+ -- bluefinRace ::+ -- ('CloneableHandle' h, e1 \<: es) =>+ -- t'Bluefin.IO.IOE' e1 ->+ -- h es ->+ -- (forall e. IOE e -> h e -> t'Bluefin.Eff.Eff' e r) ->+ -- (forall e. IOE e -> h e -> Eff e r) ->+ -- Eff es r+ -- bluefinRace io h m1 m2 = withEffToIOCloneHandle io h $ \\runInIO -> do+ -- either id id+ -- \<$\> Control.Concurrent.Async.race+ -- (runInIO $ \\io' h' -> m1 io' h')+ -- (runInIO $ \\io' h' -> m2 io' h')+ -- @+ --+ -- Then you can safely use it to race Bluefin @Eff@ actions:+ --+ -- @+ -- example :: IO ()+ -- example = 'Bluefin.Eff.runEff' $ \\io -> 'Bluefin.State.evalState' 0 $ \\st -> do+ -- r \<- 'Bluefin.Exception.try' $ \\ex -> do+ -- bluefinRace+ -- io+ -- (MkMyHandle ('Bluefin.Handle.mapHandle' ex) (mapHandle st))+ -- ( \\_ (MkMyHandle ex' st') -> do+ -- 'Bluefin.State.modify' st' (subtract 2000)+ -- 'Bluefin.Exception.throw' ex' "Aborting from branch 1"+ -- )+ -- ( \\_ (MkMyHandle _ st') -> do+ -- modify st' (+ 3000)+ -- pure (2 :: Int)+ -- )+ --+ -- s <- 'Bluefin.State.get' st+ -- 'Bluefin.IO.effIO' io (print r)+ -- effIO io (putStrLn ("State started at 0 and was cloned. Now: " <> show s))+ -- @+ --+ -- You can see from the output that the actions were raced as+ -- expected, and the @State@ was cloned so that changes to it in+ -- the branches of @race@ did not affect the original @State@.+ --+ -- @+ -- -- Run one time (the first thread was faster)+ -- ghci> example+ -- Right 2+ -- State started at 0 and was cloned. Now: 0+ -- -- Run another time (the second thread was faster)+ -- ghci> example+ -- Left "Aborting from branch 1"+ -- State started at 0 and was cloned. Now: 0+ -- @+ --+ -- Note that @withEffToIOCloneHandle@ only allows access to /one/+ -- external @Handle@ within it, so if you have several you'd like+ -- to use you'll have to define a new handle that bundles them+ -- together.+ withEffToIOCloneHandle,++ -- * @CloneableHandle@+ CloneableHandle,+ GenericCloneableHandle (MkGenericCloneableHandle),+ GCloneableHandle,++ -- * @GHC.Generics@ re-exports+ Generic1,+ )+where++import Bluefin.Internal.CloneableHandle
src/Bluefin/Compound.hs view
@@ -7,22 +7,22 @@ -- creating your own effects is equivalent to creating your own -- data types. We just use the techniques we know and love from -- Haskell! For example, if I want to make a "counter" effect- -- that allows me to increment a counter then I can wrap a 'Bluefin.State.State'- -- handle in a newtype:+ -- that allows me to increment a counter then I can wrap a 'Bluefin.Capability.Modify.Modify'+ -- capability in a newtype: -- -- @- -- newtype Counter1 e = MkCounter1 ('Bluefin.State.State' Int e)+ -- newtype Counter1 e = MkCounter1 ('Bluefin.Capability.Modify.Modify' Int e) --- -- incCounter1 :: (e :> es) => Counter1 e -> 'Bluefin.Eff.Eff' es ()- -- incCounter1 (MkCounter1 st) = 'Bluefin.State.modify' st (+ 1)+ -- incCounter1 :: (e \<: es) => Counter1 e -> 'Bluefin.Eff.Eff' es ()+ -- incCounter1 (MkCounter1 st) = 'Bluefin.Capability.Modify..modify' st (+ 1) -- -- runCounter1 :: -- (forall e. Counter1 e -> Eff (e :& es) r) -> -- Eff es Int -- runCounter1 k =- -- 'Bluefin.State.evalState' 0 $ \\st -> do+ -- 'Bluefin.Capability.Modify.evalModify' 0 $ \\st -> do -- _ <- k (MkCounter1 st)- -- 'Bluefin.State.get' st+ -- 'Bluefin.Capability.Modify.get' st -- @ -- -- Running the handler tells me the number of times I incremented@@ -47,29 +47,29 @@ -- normal approach we use to wrap multiple values into a single -- value: define a new data type with multiple fields. There's a -- caveat to this approach, but before we address the caveat let's- -- see the approach in action. Here we define a new handle,- -- @Counter2@, that contains a 'Bluefin.State.State' and 'Bluefin.Exception.Exception' handle+ -- see the approach in action. Here we define a new capabiilty,+ -- @Counter2@, that contains a 'Bluefin.Capability.Modify.Modify' and 'Bluefin.Capability.Throw.Throw' capability -- within it. That allows us to increment the counter and throw -- an exception when we hit a limit. -- -- @- -- data Counter2 e1 e2 = MkCounter2 ('Bluefin.State.State' Int e1) ('Bluefin.Exception.Exception' () e2)+ -- data Counter2 e1 e2 = MkCounter2 ('Bluefin.Capability.Modify.Modify' Int e1) ('Bluefin.Capability.Throw.Throw' () e2) --- -- incCounter2 :: (e1 :> es, e2 :> es) => Counter2 e1 e2 -> 'Bluefin.Eff.Eff' es ()+ -- incCounter2 :: (e1 \<: es, e2 \<: es) => Counter2 e1 e2 -> 'Bluefin.Eff.Eff' es () -- incCounter2 (MkCounter2 st ex) = do- -- count <- 'Bluefin.State.get' st+ -- count <- 'Bluefin.Capabiilty.Modify.get' st -- when (count >= 10) $- -- 'Bluefin.Exception.throw' ex ()- -- 'Bluefin.State.put' st (count + 1)+ -- 'Bluefin.Capability.Throw.throw' ex ()+ -- 'Bluefin.Capability.Modify.put' st (count + 1) -- -- runCounter2 :: -- (forall e1 e2. Counter2 e1 e2 -> Eff (e2 :& e1 :& es) r) -> -- Eff es Int -- runCounter2 k =- -- 'Bluefin.State.evalState' 0 $ \\st -> do- -- _ \<- 'Bluefin.Exception.try' $ \\ex -> do+ -- 'Bluefin.Capability.Modify.evalModify' 0 $ \\st -> do+ -- _ \<- 'Bluefin.Capability.Throw.try' $ \\ex -> do -- k (MkCounter2 st ex)- -- 'Bluefin.State.get' st+ -- 'Bluefin.Capability.Modify.get' st -- @ -- -- We can see that attempting to increment the counter fovever@@ -88,7 +88,7 @@ -- @ -- -- The flaw of this approach is that you expose one effect- -- parameter for each handle in the data type. That's rather+ -- parameter for each capability in the data type. That's rather -- cumbersome! We can do better. -- ** Wrap multiple effects, a better approach@@ -97,27 +97,27 @@ -- expose a single one. To make this work we have to define our -- handler in a slightly different way. Firstly we apply -- 'useImplIn' to the effectful operation @k@ and secondly we- -- apply 'mapHandle' to each of the handles out of which we create- -- our compound handle. Everything else remains the same.+ -- apply 'mapHandle' to each of the capabiilties out of which we create+ -- our compound capability. Everything else remains the same. -- -- @- -- data Counter3 e = MkCounter3 ('Bluefin.State.State' Int e) ('Bluefin.Exception.Exception' () e)+ -- data Counter3 e = MkCounter3 ('Bluefin.Capability.Modify.Modify' Int e) ('Bluefin.Capability.Throw.Throw' () e) --- -- incCounter3 :: (e :> es) => Counter3 e -> Eff es ()+ -- incCounter3 :: (e \<: es) => Counter3 e -> Eff es () -- incCounter3 (MkCounter3 st ex) = do- -- count <- 'Bluefin.State.get' st+ -- count <- 'Bluefin.Capability.Modify.get' st -- when (count >= 10) $- -- 'Bluefin.Exception.throw' ex ()- -- 'Bluefin.State.put' st (count + 1)+ -- 'Bluefin.Capability.Throw.throw' ex ()+ -- 'Bluefin.Capability.Modify.put' st (count + 1) -- -- runCounter3 :: -- (forall e. Counter3 e -> Eff (e :& es) r) -> -- Eff es Int -- runCounter3 k =- -- 'Bluefin.State.evalState' 0 $ \\st -> do- -- _ \<- 'Bluefin.Exception.try' $ \\ex -> do+ -- 'Bluefin.Capability.Modify.evalModify' 0 $ \\st -> do+ -- _ \<- 'Bluefin.Capability.Throw.try' $ \\ex -> do -- 'useImplIn' k (MkCounter3 ('mapHandle' st) (mapHandle ex))- -- 'Bluefin.State.get' st+ -- 'Bluefin.Capability.Modify.get' st -- @ -- -- The example works as before:@@ -141,19 +141,19 @@ -- though: we can leave an effect unhandled to be handled by a -- different handler at a higher level. This must /always/ be the -- case for 'Bluefin.IO.IOE', which can only be handled at the top- -- level by 'Bluefin.Eff.runEff_'. Let's see what it looks like to+ -- level by 'Bluefin.Eff.runEff'. Let's see what it looks like to -- wrap @IOE@ and provide an API which allows a subset of @IO@ -- operations. -- -- @ -- newtype Counter3B e = MkCounter3B ('Bluefin.IO.IOE' e) --- -- incCounter3B :: (e :> es) => Counter3B e -> 'Bluefin.Eff.Eff' es ()+ -- incCounter3B :: (e \<: es) => Counter3B e -> 'Bluefin.Eff.Eff' es () -- incCounter3B (MkCounter3B io) = -- effIO io (putStrLn "You tried to increment the counter") -- -- runCounter3B ::- -- (e1 :> es) =>+ -- (e1 \<: es) => -- IOE e1 -> -- (forall e. Counter3B e -> Eff (e :& es) r) -> -- Eff es r@@ -162,7 +162,7 @@ -- -- @ -- exampleCounter3B :: IO ()- -- exampleCounter3B = 'Bluefin.Eff.runEff_' $ \\io -> runCounter3B io $ \\c -> do+ -- exampleCounter3B = 'Bluefin.Eff.runEff' $ \\io -> runCounter3B io $ \\c -> do -- incCounter3B c -- incCounter3B c -- incCounter3B c@@ -191,32 +191,32 @@ -- -- @ -- data Counter4 e- -- = MkCounter4 ('Bluefin.State.State' Int e) ('Bluefin.Exception.Exception' () e) ('Bluefin.Stream.Stream' String e)+ -- = MkCounter4 ('Bluefin.Capability.Modify.Modify' Int e) ('Bluefin.Capability.Throw.Throw' () e) ('Bluefin.Stream.Stream' String e) --- -- incCounter4 :: (e :> es) => Counter4 e -> Eff es ()+ -- incCounter4 :: (e \<: es) => Counter4 e -> Eff es () -- incCounter4 (MkCounter4 st ex y) = do- -- count <- 'Bluefin.State.get' st+ -- count <- 'Bluefin.Capability.Modify.get' st -- -- when (even count) $ -- 'Bluefin.Stream.yield' y "Count was even" -- -- when (count >= 10) $- -- 'Bluefin.Exception.throw' ex ()+ -- 'Bluefin.Capability.Throw.throw' ex () --- -- 'Bluefin.State.put' st (count + 1)+ -- 'Bluefin.Capability.Modify.put' st (count + 1) --- -- getCounter4 :: (e :> es) => Counter4 e -> String -> Eff es Int+ -- getCounter4 :: (e \<: es) => Counter4 e -> String -> Eff es Int -- getCounter4 (MkCounter4 st _ y) msg = do -- yield y msg -- get st -- -- runCounter4 ::- -- (e1 :> es) =>+ -- (e1 \<: es) => -- Stream String e1 -> -- (forall e. Counter4 e -> Eff (e :& es) r) -> -- Eff es Int -- runCounter4 y k =- -- evalState 0 $ \\st -> do+ -- evalModify 0 $ \\st -> do -- _ \<- try $ \\ex -> do -- 'useImplIn' k (MkCounter4 ('mapHandle' st) (mapHandle ex) (mapHandle y)) -- get st@@ -244,55 +244,53 @@ -- new effects implemented in terms of specific other effects. We -- can also define dynamic effects, whose implementation is left -- abstract, to be defined in the handler. To do that we create a- -- handle that is a record of functions. To run an effectful+ -- capability that is a record of functions. To run an effectful -- operation we call one of the functions from the record. We -- define the record in the handler. Here @incCounter5Impl@ and -- @getCounter5Impl@ are exactly the same as @incCounter4@ and -- @getCounter4@ were, they're just defined in the handler. In -- order to be used polymorphically, the actually effectful -- functions we call, @incCounter5@ and @getCounter5@ are derived- -- from the record fields by applying 'makeOp'.+ -- from the record fields. -- -- @ -- data Counter5 e = MkCounter5- -- { incCounter5Impl :: forall e'. 'Bluefin.Eff.Eff' (e' :& e) (),- -- getCounter5Impl :: forall e'. String -> Eff (e' :& e) Int+ -- { incCounter5Impl :: 'Bluefin.Eff.Eff' e (),+ -- getCounter5Impl :: String -> Eff e Int -- }+ -- deriving (Generic)+ -- deriving (Handle) via 'OneWayCoercibleHandle' Counter5 --- -- instance 'Handle' Counter5 where- -- mapHandle c =- -- MkCounter5- -- { incCounter5Impl = 'useImplUnder' (incCounter5Impl c),- -- getCounter5Impl = \\msg -> useImplUnder (getCounter5Impl c msg)- -- }+ -- instance (e \<: es) => 'OneWayCoercible.OneWayCoercible' (Counter5 e) (Counter5 es) where+ -- oneWayCoercibleImpl = 'OneWayCoercible.gOneWayCoercible' --- -- incCounter5 :: (e :> es) => Counter5 e -> Eff es ()- -- incCounter5 e = 'makeOp' (incCounter5Impl ('mapHandle' e))+ -- incCounter5 :: (e \<: es) => Counter5 e -> Eff es ()+ -- incCounter5 e = incCounter5Impl ('mapHandle' e) --- -- getCounter5 :: (e :> es) => Counter5 e -> String -> Eff es Int- -- getCounter5 e msg = makeOp (getCounter5Impl (mapHandle e) msg)+ -- getCounter5 :: (e \<: es) => Counter5 e -> String -> Eff es Int+ -- getCounter5 e msg = getCounter5Impl (mapHandle e) msg -- -- runCounter5 ::- -- (e1 :> es) =>+ -- (e1 \<: es) => -- Stream String e1 -> -- (forall e. Counter5 e -> Eff (e :& es) r) -> -- Eff es Int -- runCounter5 y k =- -- 'Bluefin.State.evalState' 0 $ \\st -> do- -- _ \<- 'Bluefin.Exception.try' $ \\ex -> do+ -- 'Bluefin.Capability.Modify.evalModify' 0 $ \\st -> do+ -- _ \<- 'Bluefin.Capability.Throw.try' $ \\ex -> do -- 'useImplIn' -- k -- ( MkCounter5 -- { incCounter5Impl = do- -- count <- 'Bluefin.State.get' st+ -- count <- 'Bluefin.Capability.Modify.get' st -- -- when (even count) $ -- 'Bluefin.Stream.yield' y "Count was even" -- -- when (count >= 10) $- -- 'Bluefin.Exception.throw' ex ()+ -- 'Bluefin.Capability.Throw.throw' ex () --- -- 'Bluefin.State.put' st (count + 1),+ -- 'Bluefin.Capability.Modify.put' st (count + 1), -- getCounter5Impl = \\msg -> do -- yield y msg -- get st@@ -324,52 +322,49 @@ -- | We can also freely combine concrete and dynamic effects. In -- the following example, the @incCounter6@ effect is left -- dynamic, and defined in the handler, whilst @getCounter6@ is- -- implemented in terms of concrete 'Bluefin.State.State' and 'Bluefin.Stream.Stream' effects.+ -- implemented in terms of concrete 'Bluefin.Capability.Modify.Modify' and 'Bluefin.Stream.Stream' effects. -- -- @ -- data Counter6 e = MkCounter6- -- { incCounter6Impl :: forall e'. 'Bluefin.Eff.Eff' (e' :& e) (),- -- counter6State :: 'Bluefin.State.State' Int e,+ -- { incCounter6Impl :: 'Bluefin.Eff.Eff' e (),+ -- counter6State :: 'Bluefin.Capability.Modify.Modify' Int e, -- counter6Stream :: 'Bluefin.Stream.Stream' String e -- }+ -- deriving (Generic)+ -- deriving (Handle) via 'OneWayCoercibleHandle' Counter6 --- -- instance 'Handle' Counter6 where- -- mapHandle c =- -- MkCounter6- -- { incCounter6Impl = 'useImplUnder' (incCounter6Impl c),- -- counter6State = 'mapHandle' (counter6State c),- -- counter6Stream = mapHandle (counter6Stream c)- -- }+ -- instance (e \<: es) => 'OneWayCoercible.OneWayCoercible' (Counter6 e) (Counter6 es) where+ -- oneWayCoercibleImpl = 'OneWayCoercible.gOneWayCoercible' --- -- incCounter6 :: (e :> es) => Counter6 e -> Eff es ()- -- incCounter6 e = 'makeOp' (incCounter6Impl (mapHandle e))+ -- incCounter6 :: (e \<: es) => Counter6 e -> Eff es ()+ -- incCounter6 e = incCounter6Impl (mapHandle e) --- -- getCounter6 :: (e :> es) => Counter6 e -> String -> Eff es Int+ -- getCounter6 :: (e \<: es) => Counter6 e -> String -> Eff es Int -- getCounter6 (MkCounter6 _ st y) msg = do -- yield y msg -- get st -- -- runCounter6 ::- -- (e1 :> es) =>+ -- (e1 \<: es) => -- Stream String e1 -> -- (forall e. Counter6 e -> Eff (e :& es) r) -> -- Eff es Int -- runCounter6 y k =- -- 'Bluefin.State.evalState' 0 $ \\st -> do- -- _ \<- 'Bluefin.Exception.try' $ \\ex -> do+ -- 'Bluefin.Capability.Modify.evalModify' 0 $ \\st -> do+ -- _ \<- 'Bluefin.Capability.Throw.try' $ \\ex -> do -- 'useImplIn' -- k -- ( MkCounter6 -- { incCounter6Impl = do- -- count <- 'Bluefin.State.get' st+ -- count <- 'Bluefin.Capability.Modify.get' st -- -- when (even count) $ -- 'Bluefin.Stream.yield' y "Count was even" -- -- when (count >= 10) $- -- 'Bluefin.Exception.throw' ex ()+ -- 'Bluefin.Capability.Throw.throw' ex () --- -- 'Bluefin.State.put' st (count + 1),+ -- 'Bluefin.Capability.Modify.put' st (count + 1), -- counter6State = mapHandle st, -- counter6Stream = mapHandle y -- }@@ -397,19 +392,24 @@ -- ** Dynamic effects with handles as arguments - -- | We can implement dynamic effects that themselves take handles- -- as arguments, by giving all the handle arguments the effect tag+ -- | We can implement dynamic effects that themselves take capabilities+ -- as arguments, by giving all the capability arguments the effect tag -- @e'@. -- -- @ -- data Counter7 e = MkCounter7- -- { incCounter7Impl :: forall e'. 'Bluefin.Exception.Exception' () e' -> 'Bluefin.Eff.Eff' (e' :& e) (),- -- counter7State :: 'Bluefin.State.State' Int e,+ -- { incCounter7Impl :: forall e'. 'Bluefin.Capability.Throw.Throw' () e' -> 'Bluefin.Eff.Eff' (e' :& e) (),+ -- counter7State :: 'Bluefin.Capability.Modify.Modify' Int e, -- counter7Stream :: 'Bluefin.Stream.Stream' String e -- }+ -- deriving (Handle) via OneWayCoercibleHandle Counter7 --- -- instance 'Handle' Counter7 where- -- mapHandle c =+ -- -- | The "forall" in the type of @incCounter7@ means that we+ -- -- can't derive the @OneWayCoercible@ instance with+ -- -- 'OneWayCoercible.gOneWayCoercible' so instead we use 'oneWayCoercibleTrustMe'.+ --+ -- instance (e \<: es) => 'OneWayCoercible' (Counter7 e) (Counter7 es) where+ -- oneWayCoercibleImpl = oneWayCoercibleTrustMe $ \\c -> -- MkCounter7 -- { incCounter7Impl = \\ex -> 'useImplUnder' (incCounter7Impl c ex), -- counter7State = 'mapHandle' (counter7State c),@@ -417,35 +417,35 @@ -- } -- -- incCounter7 ::- -- (e :> es, e1 :> es) => Counter7 e -> Exception () e1 -> Eff es ()+ -- (e \<: es, e1 \<: es) => Counter7 e -> Throw () e1 -> Eff es () -- incCounter7 e ex = 'makeOp' (incCounter7Impl ('mapHandle' e) (mapHandle ex)) --- -- getCounter7 :: (e :> es) => Counter7 e -> String -> Eff es Int+ -- getCounter7 :: (e \<: es) => Counter7 e -> String -> Eff es Int -- getCounter7 (MkCounter7 _ st y) msg = do -- yield y msg -- get st -- -- runCounter7 ::- -- (e1 :> es) =>+ -- (e1 \<: es) => -- Stream String e1 -> -- (forall e. Counter7 e -> Eff (e :& es) r) -> -- Eff es Int -- runCounter7 y k =- -- 'Bluefin.State.evalState' 0 $ \\st -> do+ -- 'Bluefin.Capability.Modify.evalModify' 0 $ \\st -> do -- _ \<- -- 'useImplIn' -- k -- ( MkCounter7 -- { incCounter7Impl = \\ex -> do- -- count \<- 'Bluefin.State.get' st+ -- count \<- 'Bluefin.Capability.Modify.get' st -- -- when (even count) $ -- 'Bluefin.Stream.yield' y "Count was even" -- -- when (count >= 10) $- -- 'Bluefin.Exception.throw' ex ()+ -- 'Bluefin.Capability.Throw.throw' ex () --- -- 'Bluefin.State.put' st (count + 1),+ -- 'Bluefin.Capability.Modify.put' st (count + 1), -- counter7State = mapHandle st, -- counter7Stream = mapHandle y -- }@@ -494,25 +494,33 @@ -- -- @ -- data DynamicReader r e = DynamicReader- -- { askLRImpl :: forall e'. 'Bluefin.Eff.Eff' (e' :& e) r,+ -- { askLRImpl :: 'Bluefin.Eff.Eff' e r, -- localLRImpl :: forall e' a. (r -> r) -> Eff e' a -> Eff (e' :& e) a -- }+ -- deriving (Handle) via OneWayCoercibleHandle (DynamicReader r) --- -- instance 'Handle' (DynamicReader r) where- -- mapHandle h =+ -- -- | The "forall" in the type of @localRImpl@ means that we+ -- -- can't derive the @OneWayCoercible@ instance with+ -- -- 'OneWayCoercible.gOneWayCoercible' instead we use 'oneWayCoercibleTrustMe'.+ --+ -- instance+ -- (e \<: es) =>+ -- OneWayCoercible (DynamicReader r e) (DynamicReader r es)+ -- where+ -- oneWayCoercibleImpl = oneWayCoercibleTrustMe $ \\h -> -- DynamicReader- -- { askLRImpl = 'useImplUnder' (askLRImpl h),+ -- { askLRImpl = 'useImpl' (askLRImpl h), -- localLRImpl = \\f k -> useImplUnder (localLRImpl h f k) -- } -- -- askLR ::- -- (e :> es) =>+ -- (e \<: es) => -- DynamicReader r e -> -- Eff es r -- askLR c = 'makeOp' (askLRImpl ('mapHandle' c)) -- -- localLR ::- -- (e :> es) =>+ -- (e \<: es) => -- DynamicReader r e -> -- (r -> r) -> -- Eff es a ->@@ -543,22 +551,21 @@ -- -- @ -- data FileSystem es = MkFileSystem- -- { readFileImpl :: forall e. FilePath -> 'Bluefin.Eff.Eff' (e :& es) String,- -- writeFileImpl :: forall e. FilePath -> String -> Eff (e :& es) ()+ -- { readFileImpl :: FilePath -> 'Bluefin.Eff.Eff' es String,+ -- writeFileImpl :: FilePath -> String -> Eff es () -- }+ -- deriving (Generic)+ -- deriving (Handle) via 'OneWayCoercibleHandle' FileSystem --- -- instance 'Handle' FileSystem where- -- mapHandle fs = MkFileSystem {- -- readFileImpl = \\fp -> 'useImplUnder' (readFileImpl fs fp),- -- writeFileImpl = \\fp s -> useImplUnder (writeFileImpl fs fp s)- -- }+ -- instance (e \<: es) => 'OneWayCoercible.OneWayCoercible' (FileSystem e) (FileSystem es) where+ -- oneWayCoercibleImpl = 'OneWayCoercible.gOneWayCoercible' --- -- readFile :: (e :> es) => FileSystem e -> FilePath -> Eff es String- -- readFile fs filepath = 'makeOp' (readFileImpl ('mapHandle' fs) filepath)+ -- readFile :: (e \<: es) => FileSystem e -> FilePath -> Eff es String+ -- readFile fs filepath = readFileImpl ('mapHandle' fs) filepath --- -- writeFile :: (e :> es) => FileSystem e -> FilePath -> String -> Eff es ()+ -- writeFile :: (e \<: es) => FileSystem e -> FilePath -> String -> Eff es () -- writeFile fs filepath contents =- -- makeOp (writeFileImpl (mapHandle fs) filepath contents)+ -- writeFileImpl (mapHandle fs) filepath contents -- @ -- -- We can make a pure handler that simulates reading and writing@@ -567,24 +574,24 @@ -- -- @ -- runFileSystemPure ::- -- (e1 :> es) =>- -- Exception String e1 ->+ -- (e1 \<: es) =>+ -- Throw String e1 -> -- [(FilePath, String)] -> -- (forall e2. FileSystem e2 -> Eff (e2 :& es) r) -> -- Eff es r -- runFileSystemPure ex fs0 k =- -- 'Bluefin.State.evalState' fs0 $ \\fs ->+ -- 'Bluefin.Capability.Modify.evalModify' fs0 $ \\fs -> -- 'useImplIn' -- k -- MkFileSystem -- { readFileImpl = \\filepath -> do- -- fs' <- 'Bluefin.State.get' fs+ -- fs' <- 'Bluefin.Capability.Modify.get' fs -- case lookup filepath fs' of -- Nothing ->- -- 'Bluefin.Exception.throw' ex ("File not found: " <> filepath)+ -- 'Bluefin.Capability.Throw.throw' ex ("File not found: " <> filepath) -- Just s -> pure s, -- writeFileImpl = \\filepath contents ->- -- 'Bluefin.State.modify' fs ((filepath, contents) :)+ -- 'Bluefin.Capability.Modify.modify' fs ((filepath, contents) :) -- } -- @ --@@ -594,8 +601,8 @@ -- @ -- runFileSystemIO :: -- forall e1 e2 es r.- -- (e1 :> es, e2 :> es) =>- -- Exception String e1 ->+ -- (e1 \<: es, e2 \<: es) =>+ -- Throw String e1 -> -- IOE e2 -> -- (forall e. FileSystem e -> Eff (e :& es) r) -> -- Eff es r@@ -609,10 +616,10 @@ -- \\filepath -> adapt . Prelude.writeFile filepath -- } -- where- -- adapt :: (e1 :> ess, e2 :> ess) => IO a -> Eff ess a+ -- adapt :: (e1 \<: ess, e2 \<: ess) => IO a -> Eff ess a -- adapt m = -- effIO io (Control.Exception.try @IOException m) >>= \\case- -- Left e -> 'Bluefin.Exception.throw' ex (show e)+ -- Left e -> 'Bluefin.Capability.Throw.throw' ex (show e) -- Right r -> pure r -- @ --@@ -620,7 +627,7 @@ -- does some file system operations. -- -- @- -- action :: (e :> es) => FileSystem e -> Eff es String+ -- action :: (e \<: es) => FileSystem e -> Eff es String -- action fs = do -- file <- readFile fs "\/dev\/null" -- when (length file == 0) $ do@@ -632,7 +639,7 @@ -- -- @ -- exampleRunFileSystemPure :: Either String String- -- exampleRunFileSystemPure = 'Bluefin.Eff.runPureEff' $ 'Bluefin.Exception.try' $ \\ex ->+ -- exampleRunFileSystemPure = 'Bluefin.Eff.runPureEff' $ 'Bluefin.Capability.Throw.try' $ \\ex -> -- runFileSystemPure ex [("\/dev\/null", "")] action -- @ --@@ -645,7 +652,7 @@ -- -- @ -- exampleRunFileSystemIO :: IO (Either String String)- -- exampleRunFileSystemIO = 'Bluefin.Eff.runEff_' $ \\io -> try $ \\ex ->+ -- exampleRunFileSystemIO = 'Bluefin.Eff.runEff' $ \\io -> try $ \\ex -> -- runFileSystemIO ex io action -- @ --@@ -657,7 +664,26 @@ -- @ -- * Functions for making compound effects- Handle (mapHandle),++ -- ** @Handle@+ Handle (handleImpl),+ HandleD,+ mapHandle,++ -- ** @OneWayCoercible@++ -- | The documentation for 'Handle' shows how to use+ -- @OneWayCoercible@ to define @Handle@ instances.+ OneWayCoercible.OneWayCoercible (OneWayCoercible.oneWayCoercibleImpl),+ OneWayCoercibleHandle (MkOneWayCoercibleHandle),+ handleOneWayCoercible,+ withHandle,+ OneWayCoercible.gOneWayCoercible,+ oneWayCoercibleTrustMe,+ -- | Bluefin re-exports @Generic@ for convenience.+ OneWayCoercible.Generic,++ -- ** Other functions for compound effects makeOp, useImpl, useImplUnder,@@ -674,3 +700,4 @@ where import Bluefin.Internal+import qualified Bluefin.Internal.OneWayCoercible as OneWayCoercible
src/Bluefin/Consume.hs view
@@ -1,14 +1,21 @@+-- | This is an old interface and will be deprecated in the+-- future. You are encouraged to use "Bluefin.Capability.Await" instead. module Bluefin.Consume ( -- | 'Consume' allows you to await values during the execution of -- a Bluefin operation. It provides similar functionality to -- @await@ from Conduit or Pipes.+ --+ -- For information about prompt finalization/resource safety when+ -- using Bluefin @Consume@s, see "Bluefin.Coroutine". -- * Handle Consume,+ -- * Handlers consumeEach, consumeStream, streamConsume,+ -- * Effectful operations await, takeConsume,
src/Bluefin/Coroutine.hs view
@@ -1,11 +1,28 @@+-- | This is an old interface and will be deprecated in the+-- future. You are encouraged to use "Bluefin.Capability.Request"+-- instead. module Bluefin.Coroutine ( -- | @Coroutine@ allows to yield values and receive results back. -- [Wikipedia -- suggests](https://en.wikipedia.org/wiki/Coroutine#Definition_and_types) -- that Bluefin's coroutines are "second-class stackful- -- coroutines". This module is not documented yet. You might+ -- coroutines". This module is not documented much yet. You might -- want to start with "Bluefin.Stream", which is the most common -- way to use coroutines.++ -- ** Prompt finalization/resource safety++ -- | Bluefin+ -- 'Bluefin.Stream.Stream'\/'Bluefin.Consume.Consume'\/'Bluefin.Coroutine.Coroutine'+ -- computations have much better resource safety properties than+ -- Conduit and Pipes. You can use+ -- @Bluefin.Eff.'Bluefin.Eff.bracket'@ within a streaming+ -- computation and the acquired resource is guaranteed to be+ -- released and the end of the bracket, rather than at the end of+ -- the @ResourceT@ scope as it is the case in Conduit and Pipes.+ -- See the blog post [Bluefin streams finalize+ -- promptly](https://h2.jaguarpaw.co.uk/posts/bluefin-streams-finalize-promptly/)+ -- for more details. -- * Handle Coroutine,
+ src/Bluefin/DslBuilder.hs view
@@ -0,0 +1,341 @@+module Bluefin.DslBuilder+ ( -- | Haksell is great for writing domain specific languages (DSLs)+ -- and @Bluefin.DslBuilder@ provides an easy way to write DSLs+ -- using Bluefin.++ -- * Robot arena example++ -- ** Data types for the robot arena++ -- | Here's an example of the use of @Bluefin.DslBuilder@. Suppose+ -- we have a data type that represents the location of robots and+ -- obstacles in a two-dimensional square arena:+ --+ -- @+ -- data Arena = MkArena+ -- { arenaRobots :: [RobotEntry],+ -- arenaObstacles :: [ObstacleEntry]+ -- }+ -- @+ --+ -- Each @RobotEntry@ stores the robot's name, coordinates on the+ -- 2d grid, facing direction, and instructions for robot to carry+ -- out+ --+ -- @+ -- type RobotEntry = (String, (Int, Int), Direction, [Instruction])+ -- @+ --+ -- The instructions that a robot can perform are to wait for a+ -- given number of time units, move forward, turn left and turn+ -- right.+ --+ -- @+ -- data Instruction = Wait Int | Forward | TurnLeft | TurnRight+ -- data Direction = N | E | S | W+ -- @+ --+ -- The @ObstacleEntry@s store the type of each obstacle and its+ -- coordinates on the 2d grid.+ --+ -- @+ -- type ObstacleEntry = (Obstacle, (Int, Int))+ --+ -- data Obstacle = Sand | Rock | Iron+ -- @++ -- ** Defining an @Arena@ by hand++ -- | Suppose we want an @Arena@ with a red robot which moves+ -- towards a sand obstacle, and a blue robot that is stuck behind+ -- iron obstacles and can only turn around on the spot. Here's an+ -- ASCII diagram of the initial position we want, where @r@ is the+ -- red robot, @S@ is a sand obstacle, @b@ is the blue robot and+ -- @I@ is an iron obstacle:+ --+ -- @+ -- 5|+ -- 4| III+ -- 3| IbI+ -- 2| III+ -- 1|+ -- 0|r S+ -- +------+ -- 012345+ -- @+ --+ -- We can define such an @Arena@ by hand like this:+ --+ -- @+ -- myArena :: Arena+ -- myArena =+ -- MkArena+ -- { arenaRobots =+ -- [ ("red", (0, 0), E, [Wait 100, Forward, Forward, Forward, Forward]),+ -- ("blue", (3, 3), N, [TurnRight, TurnRight, TurnRight, TurnRight, TurnRight, TurnRight, TurnRight, TurnRight, TurnRight, TurnRight, TurnRight, TurnRight, TurnRight, TurnRight, TurnRight, TurnRight, TurnRight, TurnRight, TurnRight, TurnRight])+ -- ],+ -- arenaObstacles = [(Sand, (5, 0)), (Iron, (2, 2)), (Iron, (2, 3)), (Iron, (2, 4)), (Iron, (3, 2)), (Iron, (3, 4)), (Iron, (4, 2)), (Iron, (4, 3)), (Iron, (4, 4))]+ -- }+ -- @+ --+ -- That's messy and contains a lot of repetition! We can use+ -- Haskell constructs to do a bit better:+ --+ -- @+ -- myArena2 :: Arena+ -- myArena2 =+ -- MkArena+ -- { arenaRobots =+ -- [ ("red", (0, 0), E, Wait 100 : replicate 4 Forward),+ -- ("blue", (3, 3), N, replicate 20 TurnRight)+ -- ],+ -- arenaObstacles =+ -- (Sand, (5, 0))+ -- : [ (Iron, (x, y))+ -- | x <- [2 .. 4],+ -- y <- [2 .. 4],+ -- (x, y) /= (3, 3)+ -- ]+ -- }+ -- @+ --+ -- That's more compressed but it doesn't describe our intent+ -- clearly.++ -- ** Defining an @Arena@ with a DSL++ -- | Let's use @Bluefin.DslBuilder@ to write a DSL that allows us+ -- to express our intent more clearly. Before we define the DSL,+ -- let's look at what it will allow us to write. In @myDslArena@+ -- below I can define the red robot and its obstacles separately+ -- from the blue robot and its obstacles, and I can use `for_`+ -- loops to conveniently define the blue robot's iron cage.+ --+ -- @+ -- myDslArena :: Arena+ -- myDslArena = buildArena $ do+ -- -- 5|+ -- -- 4|+ -- -- 3|+ -- -- 2|+ -- -- 1|+ -- -- 0|R S+ -- -- +------+ -- -- 012345+ -- robot "red" (0, 0) E $ do+ -- wait 100+ -- forward 4+ --+ -- obstacle Sand (5, 0)+ --+ -- -- 5|+ -- -- 4| III+ -- -- 3| IBI+ -- -- 2| III+ -- -- 1|+ -- -- 0|+ -- -- +------+ -- -- 012345+ -- robot "blue" (3, 3) N $ do+ -- replicateM_ 10 aboutFace+ --+ -- for_ [2 .. 4] $ \\x -> do+ -- for_ [2 .. 4] $ \\y -> do+ -- unless ((x, y) == (3, 3)) $ do+ -- obstacle Iron (x, y)+ -- @++ -- ** Arena DSL definitions++ -- | So what are the definitions of the components that go into+ -- building an @Arena@?++ -- *** @buildArena@ and @ArenaBuilder@++ -- | Firstly, what does @buildArena@ do? It's going to have this+ -- type:+ --+ -- @+ -- buildArena :: ArenaBuilder -> Arena+ -- @+ --+ -- @ArenaBuilder@ is the type of the @do@ block which contains the+ -- @robot@ and @obstacle@ entries, and is a convenience type+ -- synonym:+ --+ -- @+ -- type ArenaBuilder = ArenaBuilder_ ()+ -- @+ --+ -- @ArenaBuilder_@ is a @Monad@ and is the first component+ -- we are going to build using @Bluefin.DslBuilder@:+ --+ -- @+ -- newtype ArenaBuilder_ r+ -- = MkArenaBuilder ('DslBuilder' ArenaH r)+ -- deriving (Functor, Applicative, Monad)+ -- @+ --+ -- @DslBuilder ArenaH@ is a @Monad@ that allows us access to the+ -- effects inside the capability @ArenaH@ (and no others). So what is+ -- @ArenaH@? It is defined like this:+ --+ -- @+ -- data ArenaH e = MkArenaH ('Bluefin.Stream.Stream' RobotEntry e) (Stream ObstacleEntry e)+ -- deriving t'Bluefin.Compound.Generic'+ -- deriving t'Bluefin.Compound.Handle' via t'Bluefin.Compound.OneWayCoercibleHandle' ArenaH+ --+ -- instance (e \<: es) => 'Bluefin.Compound.OneWayCoercible' (ArenaH e) (ArenaH es) where+ -- 'Bluefin.Compound.oneWayCoercibleImpl' = 'Bluefin.Compound.gOneWayCoercible'+ -- @+ --+ -- What does that mean? Well, @ArenaH@ is defined according to one+ -- of the recipes from "Bluefin.Compound", and gives the ability+ -- to yield to a @Stream@ of @RobotEntry@s and a stream of+ -- @ObstacleEntry@s, i.e. the components that make up an @Arena@.+ -- The only things we can do with the @ArenaH@ then are to give it+ -- @RobotEntry@s or @ObstacleEntry@s. How do we get them?++ -- *** @obstacle@++ -- | To get an @ObstacleEntry@ we use the @obstacle@ command.+ -- It has type+ --+ -- @+ -- obstacle :: Strength -> (Int, Int) -> ArenaBuilder+ -- @+ --+ -- When calling @obstacle@ we specify the strength of the obstacle+ -- and its coordinates in the arena. In fact, having those gives+ -- us exactly what we need to yield an @ObstacleEntry@ to the+ -- @Stream ObstacleEntry@ inside the @ArenaBuilder@:+ --+ -- @+ -- obstacle o coord =+ -- MkArenaBuilder $ 'dslBuilder' $ \(MkArenaH _ yobstacle) -> do+ -- 'Bluefin.Stream.yield' yobstacle (o, coord)+ -- @++ -- *** @robot@ and @InstructionsBuilder@++ -- | To get a @RobotEntry@ we use the @robot@ component. It has+ -- type+ --+ -- @+ -- robot ::+ -- String -> (Int, Int) -> Direction -> InstructionsBuilder -> ArenaBuilder+ -- @+ --+ -- When calling @robot@ we specify the name, coordinates, and+ -- facing direction for our robot. We also specify the+ -- instructions for the robot by providing an+ -- @InstructionsBuilder@? What's that? It's /another/ @Monad@+ -- defined in terms of @DslBuilder@:+ --+ -- @+ -- type InstructionsBuilder = InstructionsBuilder_ ()+ --+ -- newtype InstructionsBuilder_ r+ -- = MkInstructionsBuilder ('DslBuilder' InstructionsH r)+ -- deriving (Functor, Applicative, Monad)+ -- @+ --+ -- Like with @ArenaBuilder@, to define the @Monad@ we define a+ -- capability, this time @InstructionsH@:+ --+ -- @+ -- data InstructionsH e = MkInstructionsH ('Bluefin.Stream.Stream' Instruction e)+ -- deriving t'Bluefin.Compound.Generic'+ -- deriving t'Bluefin.Compound.Handle' via t'Bluefin.Compound.OneWayCoercibleHandle' InstructionsH+ --+ -- instance (e \<: es) => t'Bluefin.Compound.OneWayCoercible' (InstructionsH e) (InstructionsH es) where+ -- oneWayCoercibleImpl = 'Bluefin.Compound.gOneWayCoercible'+ -- @+ --+ -- @InstructionsH@ allows us to yield to a sequence of+ -- @Instruction@s, i.e. the type of robot instructions defined+ -- above and used in @Arena@ via @RobotEntry@. In fact, the job+ -- of @robot@ is exactly to allow us to define a @RobotEntry@ and+ -- yield it to the @Stream RobotEntry@ of @Arena@:+ --+ -- @+ -- robot name coords dir (MkInstructionsBuilder ibuilder) =+ -- MkArenaBuilder $ 'dslBuilder' $ \\(MkArenaH yrobot _) -> do+ -- (insns, ()) \<- 'Bluefin.Stream.yieldToList' $ \\yinsns -> do+ -- 'runDslBuilder' (MkInstructionsH ('Bluefin.Compound.mapHandle' yinsns)) ibuilder+ --+ -- 'Bluefin.Stream.yield' yrobot (name, coords, dir, insns)+ -- @++ -- *** Creating @InstructionsBuilder@s++ -- | In a @do@ block of type @InstructionsBuilder@ we want to be+ -- able to write things like @wait 100@, @forward 4@ and+ -- @aboutFace@. What are they? To define commands of type+ -- @InstructionsBuilder@ we use this convenience function:+ --+ -- @+ -- instructionsBuilder :: Instruction -> InstructionsBuilder+ -- instructionsBuilder insn =+ -- MkInstructionsBuilder $ dslBuilder $ \\(MkInstructionsH yinsn) -> do+ -- yield yinsn insn+ -- @+ --+ -- which we can use as follows to define commands as follows:+ --+ -- @+ -- wait :: Int -> InstructionsBuilder+ -- wait n = instructionsBuilder (Wait n)+ --+ -- turnLeft :: InstructionsBuilder+ -- turnLeft = instructionsBuilder TurnLeft+ --+ -- turnRight :: InstructionsBuilder+ -- turnRight = instructionsBuilder TurnRight+ --+ -- aboutFace :: InstructionsBuilder+ -- aboutFace = do+ -- turnRight+ -- turnRight+ --+ -- forward :: Int -> InstructionsBuilder+ -- forward n = replicateM_ n (instructionsBuilder Forward)+ -- @++ -- *** Implementing @buildArena@++ -- | We're now ready to implement @buildArena@. We create a+ -- @Stream RobotEntry@ and a @Stream ObstacleEntry@ to pass to the+ -- @MkArenaH@ constructor, and use @runDslBuilder@ to run the+ -- @ArenaBuilder@ provided. (This is a lot like some of the approaches+ -- in "Bluefin.Compound".)+ --+ -- @+ -- buildArena :: ArenaBuilder -> Arena+ -- buildArena (MkArenaBuilder arenaBuilder) = runPureEff $ do+ -- (robots, obstacles) \<- 'Bluefin.Stream.yieldToList' $ \\yrobots -> do+ -- (obstacles, ()) \<- yieldToList $ \\yobstacles -> do+ -- 'runDslBuilder'+ -- (MkArenaH ('Bluefin.Compound.mapHandle' yrobots) (mapHandle yobstacles))+ -- arenaBuilder+ --+ -- pure obstacles+ --+ -- pure+ -- MkArena+ -- { arenaRobots = robots,+ -- arenaObstacles = obstacles+ -- }+ -- @+ --+ -- And that's all we need to support the implementation of+ -- @myDslArena :: Arena@ given above!++ -- * @DslBuilder@+ DslBuilder,+ dslBuilder,+ runDslBuilder,+ )+where++import Bluefin.Internal.DslBuilder
+ src/Bluefin/DslBuilderEff.hs view
@@ -0,0 +1,10 @@+-- | Like "Bluefin.DslBuilder", but when you want to be able to run+-- additional effects as well.+module Bluefin.DslBuilderEff+ ( DslBuilderEff,+ dslBuilderEff,+ runDslBuilderEff,+ )+where++import Bluefin.Internal.DslBuilderEff
src/Bluefin/EarlyReturn.hs view
@@ -1,3 +1,6 @@+-- | This is an old interface and will be deprecated in the+-- future. You are encouraged to use "Bluefin.Capability.ReturnEarly"+-- instead. module Bluefin.EarlyReturn ( -- | Early return allows to define a block from which you can -- return early. Early return is implemented as an exception, and@@ -5,8 +8,10 @@ -- * Handle EarlyReturn,+ -- * Handlers withEarlyReturn,+ -- * Effectful operations returnEarly, )
src/Bluefin/Eff.hs view
@@ -1,24 +1,33 @@+{-# LANGUAGE ExplicitNamespaces #-}+ module Bluefin.Eff ( -- * 'Eff' monad Eff,+ -- * Run an 'Eff' runPureEff,- runEff_, runEff,+ -- * Resource management bracket,+ finally,+ -- * Type classes -- | See "Bluefin.Eff.IO" for the most direct way of doing I/O in -- Bluefin. If you really want to use 'MonadIO' you can use -- 'withMonadIO'.- withMonadIO, withMonadFail,+ -- * Effect tracking Effects, (:>),+ type (<:), (:&),++ -- * Deprecated+ runEff_, ) where
src/Bluefin/Exception.hs view
@@ -1,10 +1,15 @@+-- | This is an old interface and will be deprecated in the+-- future. You are encouraged to use "Bluefin.Capability.Throw"+-- instead. module Bluefin.Exception ( -- * Handle Exception,+ -- * Handlers try, handle, catch,+ -- * Effectful operations throw, rethrowIO,
+ src/Bluefin/Exception/GeneralBracket.hs view
@@ -0,0 +1,19 @@+module Bluefin.Exception.GeneralBracket+ ( -- * Effectful functions+ generalBracket,++ -- * Handle+ MakeExceptions,+ catchWithResource,+ pureMakeExceptions,+ apMakeExceptions,+ fmapMakeExceptions,++ -- * @:~>@+ (:~>),+ abstract,+ )+where++import Bluefin.Internal.CloneableHandle+import Bluefin.Internal.Exception
+ src/Bluefin/GadtEffect.hs view
@@ -0,0 +1,251 @@+module Bluefin.GadtEffect+ ( -- * Introduction++ -- | The Haskell effect systems @effectful@ and @polysemy@ allow+ -- users to define new effects by defining a GADT (generalized+ -- algebraic data type) whose contructors correspond to primitive+ -- operations of the effect, and then creating values of the GADT+ -- and interpreting them in terms of existing effects. This+ -- module provides Bluefin's equivalent. In fact, in @effectful@+ -- and @polysemy@ this is essentially the /only/ way you can+ -- create new effects. That's not true for Bluefin. Bluefin+ -- supports a rich collection of ways to create new effects, most+ -- of which are documented at "Bluefin.Compound". This particular+ -- module might be helpful for users coming from @effectful@ and+ -- @polysemy@, however.++ -- * Example filesystem effect++ -- | First we define a GADT with a constructor for each primitive+ -- operation of the effect we want to define. Here the primitive+ -- operations are to read a file, write a file and to wrap an+ -- effectful computation in a "trace" block.+ --+ -- @+ -- data FileSystem :: 'Effect' where+ -- ReadFile :: FilePath -> FileSystem m String+ -- WriteFile :: FilePath -> String -> FileSystem m ()+ -- Trace :: String -> m r -> FileSystem m r+ -- @+ --+ -- Then we need to define two instances for @FileSystem@:+ --+ -- @+ -- instance+ -- (e \<: es) =>+ -- t'Bluefin.Compound.OneWayCoercible' ('GadtEffect' FileSystem r e) (GadtEffect FileSystem r es)+ -- where+ -- 'Bluefin.Compound.oneWayCoercibleImpl' = 'oneWayCoercibleGadtEffectTrustMe' $ \\case+ -- ReadFile path -> ReadFile path+ -- WriteFile path contents -> WriteFile path contents+ -- Trace msg body -> Trace msg (useImpl body)+ --+ -- deriving via+ -- t'Bluefin.Compound.OneWayCoercibleHandle' ('GadtEffect' FileSystem r)+ -- instance+ -- t'Bluefin.Compound.Handle' (GadtEffect FileSystem r)+ -- @+ --+ -- Then we can define functions that implement the primitive+ -- effectful operations for @FileSystem@:+ --+ -- @+ -- readFile ::+ -- (e1 \<: es) =>+ -- 'Send' FileSystem e1 ->+ -- FilePath ->+ -- Eff es String+ -- readFile fc path =+ -- 'send' fc (ReadFile path)+ --+ -- writeFile ::+ -- (e1 \<: es) =>+ -- Send FileSystem e1 ->+ -- FilePath ->+ -- String ->+ -- Eff es ()+ -- writeFile fc path content =+ -- send fc (WriteFile path content)+ --+ -- trace ::+ -- (e1 \<: es) =>+ -- Send FileSystem e1 ->+ -- String ->+ -- Eff es r ->+ -- Eff es r+ -- trace fc msg body =+ -- send fc (Trace msg body)+ -- @+ --+ -- The instances and primitive effectful operations are+ -- boilerplate. @effectful@ and @polysemy@ have Template Haskell+ -- for generating their boilerplate+ -- ([@makeEffect@](https://hackage.haskell.org/package/effectful-th/docs/Effectful-TH.html#v:makeEffect)+ -- and+ -- [@makeSem@](https://hackage.haskell.org/package/polysemy-1.9.2.0/docs/Polysemy.html#v:makeSem)+ -- respectively) but there is no such thing for Bluefin yet,+ -- sorry! Please [open an+ -- issue](https://github.com/tomjaguarpaw/bluefin/issues/new) if+ -- that causes difficulties for you.+ --+ -- Finally we can write a handler for the @'Send' FileSystem@+ -- effect that gives it an interpretation via 'interpret':+ --+ -- @+ -- import System.IO qualified as IO+ --+ -- runFileSystem ::+ -- forall es e1 e2 r.+ -- (e1 \<: es, e2 \<: es) =>+ -- t'Bluefin.IO.IOE' e1 ->+ -- t'Bluefin.Exception.Exception' t'Control.Exception.IOException' e2 ->+ -- (forall e. 'Send' FileSystem e -> Eff (e :& es) r) ->+ -- Eff es r+ -- runFileSystem io ex = 'interpret' $ \\case+ -- ReadFile path ->+ -- adapt (IO.'System.IO.readFile' path)+ -- WriteFile path contents ->+ -- adapt (IO.'System.IO.writeFile' path contents)+ -- Trace msg body -> do+ -- 'Bluefin.IO.effIO' io (putStrLn ("Start: " <> msg))+ -- r <- 'Bluefin.Compound.useImpl' body+ -- effIO io (putStrLn ("End: " <> msg))+ -- pure r+ -- where+ -- -- If you don't want to write this signature you can use+ -- -- {-# LANGUAGE NoMonoLocalBinds #-}+ -- adapt :: (e1 \<: es', e2 \<: es') => IO r' -> Eff es' r'+ -- adapt m = 'Bluefin.IO.rethrowIO' io ex (effIO io m)+ -- @++ -- * @interpose@ example++ -- | If you're familiar with @effectful@'s @interpose@ function+ -- you may want to use Bluefin's equivalent. To see how, let's+ -- replicate [@effectful@'s interpose+ -- example](https://hackage-content.haskell.org/package/effectful-core-2.6.1.0/docs/Effectful-Dispatch-Dynamic.html#v:interpose). First+ -- we define a simple effect with three primitive operations:+ --+ -- @+ -- data E :: 'Effect' where+ -- Op1 :: E m ()+ -- Op2 :: E m ()+ -- Op3 :: E m ()+ -- @+ --+ -- Then we define the boilerplate instances+ --+ -- @+ -- instance+ -- (e \<: es) =>+ -- t'Bluefin.Compound.OneWayCoercible' ('GadtEffect' E r e) (GadtEffect E r es)+ -- where+ -- 'Bluefin.Compound.oneWayCoercibleImpl' = 'oneWayCoercibleGadtEffectTrustMe' $ \\case+ -- Op1 -> Op1+ -- Op2 -> Op2+ -- Op3 -> Op3+ --+ -- deriving via+ -- t'Bluefin.Compound.OneWayCoercibleHandle' (GadtEffect E r)+ -- instance+ -- t'Bluefin.Compound.Handle' (GadtEffect E r)+ -- @+ --+ -- and a handler for the @'Send' E@ effect:+ --+ -- @+ -- runE ::+ -- (e1 \<: es) =>+ -- IOE e1 ->+ -- (forall e. Send E e -> Eff (e :& es) r) ->+ -- Eff es r+ -- runE io = interpret $ \\case+ -- Op1 -> effIO io (putStrLn "op1")+ -- Op2 -> effIO io (putStrLn "op2")+ -- Op3 -> effIO io (putStrLn "op3")+ -- @+ --+ -- Before using 'interpose', let's look at a use of its simpler+ -- cousin, 'interpret':+ --+ -- @+ -- augmentOp2Interpret ::+ -- (e1 \<: es, e2 \<: es) =>+ -- IOE e2 ->+ -- Send E e1 ->+ -- (forall e. Send E e -> Eff (e :& es) r) ->+ -- Eff es r+ -- augmentOp2Interpret io fc = 'interpret' $ \\case+ -- Op2 -> effIO io (putStrLn "augmented op2") >> send fc Op2+ -- op -> 'passthrough' fc op+ -- @+ --+ -- Using 'interpose' is similar:+ --+ -- @+ -- augmentOp2Interpose ::+ -- (e1 \<: es, e2 \<: es) =>+ -- IOE e2 ->+ -- t'Bluefin.HandleReader.HandleReader' (Send E) e1 ->+ -- Eff es r ->+ -- Eff es r+ -- augmentOp2Interpose io = 'interpose' $ \\fc -> \\case+ -- Op2 -> effIO io (putStrLn "augmented op2") >> send fc Op2+ -- op -> 'passthrough' fc op+ -- @+ --+ -- And now let's see what they each do:+ --+ -- @+ -- example :: IO ()+ -- example = runEff $ \\io -> do+ -- let action fc = do+ -- send fc Op1+ -- send fc Op2+ -- send fc Op3+ --+ -- effIO io (putStrLn "-- interpret:")+ -- runE io $ \\fc -> do+ -- augmentOp2Interpret io fc $ \\fc' -> action fc'+ --+ -- effIO io (putStrLn "-- interpose:")+ -- runE io $ \\fc -> 'Bluefin.HandleReader.runHandleReader' fc $ \\hr -> do+ -- augmentOp2Interpose io hr $ 'Bluefin.HandleReader.asksHandle' hr action+ -- @+ --+ -- @+ -- ghci> example+ -- -- interpret:+ -- op1+ -- augmented op2+ -- op2+ -- op3+ -- -- interpose:+ -- op1+ -- augmented op2+ -- op2+ -- op3+ -- @++ -- * Handle+ Send,++ -- * Effectful operations+ send,+ passthrough,++ -- * Interpretation+ EffectHandler,+ interpret,+ interpose,++ -- * @Effect@+ Effect,++ -- * @GadtEffect@+ GadtEffect,+ oneWayCoercibleGadtEffectTrustMe,+ )+where++import Bluefin.Internal.GadtEffect
src/Bluefin/HandleReader.hs view
@@ -1,12 +1,33 @@+-- | This is an old interface and will be deprecated in the+-- future. You are encouraged to use+-- "Bluefin.Capability.AskCapability" instead.+--+-- 'HandleReader' is like t'Bluefin.Reader.Reader', generalized to+-- work for arbitrary t'Bluefin.Compound.Handle's. 'localHandle'+-- locally overrides the value of a capability in a well-scoped way. The+-- original capability will be restored when you exit the @localHandle@+-- block regardless of whether the exit was normal or via an+-- exception.+--+-- @HandleReader@ supports functionality similiar to @effectful@'s+-- [@interpose@](https://hackage.haskell.org/package/effectful-core/docs/Effectful-Dispatch-Dynamic.html#v:interpose)+-- and @polysemy@'s+-- [@intercept@](https://hackage.haskell.org/package/polysemy/docs/Polysemy.html#v:intercept),+-- that is, locally augmenting an effect with new behaviors. If you+-- want to do the same in Bluefin you may want to start with+-- @Bluefin.GadtEffect.'Bluefin.GadtEffect.interpose`@. module Bluefin.HandleReader- (-- * Handle- HandleReader,- -- * Handlers- runHandleReader,- -- * Effectful operations- askHandle,- localHandle,+ ( -- * Handle+ HandleReader,++ -- * Handlers+ runHandleReader,++ -- * Effectful operations+ askHandle,+ asksHandle,+ localHandle, )- where+where import Bluefin.Internal
src/Bluefin/IO.hs view
@@ -1,24 +1,31 @@ module Bluefin.IO ( -- | You can run 'IO' operations inside 'Eff'. - -- * Handle+ -- * Capability IOE,+ -- * Handlers- runEff_, runEff,+ -- * Effectful operations effIO, rethrowIO,+ -- * IO type classes withMonadIO, withEffToIO_,+ withEffToIOCloneHandle,+ -- ** @EffReader@ EffReader, effReader, runEffReader,+ -- ** Deprecated versions withEffToIO,+ runEff_, ) where import Bluefin.Internal+import Bluefin.Internal.CloneableHandle
src/Bluefin/Jump.hs view
@@ -1,3 +1,5 @@+-- | This is an old interface and will be deprecated in the+-- future. You are encouraged to use "Bluefin.Capability.Jump" instead. module Bluefin.Jump ( -- | 'Jump' allows you to jump back to a previously-set location. -- A "jump" is equivalent to an untyped early return, or more@@ -6,8 +8,10 @@ -- * Handle Jump,+ -- * Handlers withJump,+ -- * Effectful operations jumpTo, )
src/Bluefin/Pipes.hs view
@@ -1,14 +1,20 @@ -- | Reimplementation of the @pipes@ (@Pipes@) ecosystem in Bluefin.--- It primarily serves as an example of what you can do with Bluefin--- and you probably won't want to use it directly. Instead you are--- recommended to use ----- * 'Bluefin.Stream', 'Bluefin.Stream.yield'--- * 'Bluefin.Consume', 'Bluefin.Consume.await'--- * 'Bluefin.Stream.consumeStream'--- * For advanced cases only, 'Bluefin.Coroutine',--- 'Bluefin.Coroutine.yieldCoroutine' and--- 'Bluefin.Coroutine.connectCoroutines'+-- You should not use this module. It will be deprecated and removed+-- in future versions.+--+-- This module is just an example of what you can do with Bluefin and+-- as such it should be obtained from+-- [@bluefin-examples@](https://github.com/tomjaguarpaw/bluefin/tree/master/bluefin-examples)+-- if you want it. Instead of using it directly you are recommended+-- to use+--+-- * t'Bluefin.Capability.Yield.Yield', 'Bluefin.Capability.Yield.yield'+-- * t'Bluefin.Capability.Await.Await', 'Bluefin.Capability.Await.await'+-- * 'Bluefin.Capability.Yield.awaitYield'+-- * For advanced cases only, t'Bluefin.Capability.Request.Request',+-- 'Bluefin.Capability.Request.request' and+-- 'Bluefin.Capability.Request.connectRequests' -- -- See also "Bluefin.Pipes.Prelude". module Bluefin.Pipes
src/Bluefin/Pipes/Prelude.hs view
@@ -1,19 +1,25 @@--- | Reimplementation of the @pipes@ prelude (@Pipes.Prelude@) in--- Bluefin. It primarily serves as an example of what you can do with--- Bluefin and you probably won't want to use it directly. Instead--- you are recommended to use+-- | Reimplementation of the @pipes@ (@Pipes@) ecosystem in Bluefin. ----- * 'Bluefin.Stream', 'Bluefin.Stream.yield'--- * 'Bluefin.Consume', 'Bluefin.Consume.await'--- * 'Bluefin.Stream.consumeStream'--- * For advanced cases only, 'Bluefin.Coroutine',--- 'Bluefin.Coroutine.yieldCoroutine' and--- 'Bluefin.Coroutine.connectCoroutines'+-- You should not use this module. It will be deprecated and removed+-- in future versions. --+-- This module is just an example of what you can do with Bluefin and+-- as such it should be obtained from+-- [@bluefin-examples@](https://github.com/tomjaguarpaw/bluefin/tree/master/bluefin-examples)+-- if you want it. Instead of using it directly you are recommended+-- to use+--+-- * t'Bluefin.Capability.Yield.Yield', 'Bluefin.Capability.Yield.yield'+-- * t'Bluefin.Capability.Await.Await', 'Bluefin.Capability.Await.await'+-- * 'Bluefin.Capability.Yield.awaitYield'+-- * For advanced cases only, t'Bluefin.Capability.Request.Request',+-- 'Bluefin.Capability.Request.request' and+-- 'Bluefin.Capability.Request.connectRequests'+-- -- See also "Bluefin.Pipes". -- -- @--- >>> 'Bluefin.Eff.runEff_' $ \\io -> 'runEffect' $ do+-- >>> 'Bluefin.Eff.runEff' $ \\io -> 'runEffect' $ do -- 'stdinLn' io >-> 'takeWhile'' (/= "quit") >-> 'stdoutLn' io -- Test -- Test
+ src/Bluefin/Prim.hs view
@@ -0,0 +1,44 @@+-- | For defining @PrimMonad@ instances, for example:+--+-- @+-- -- Define a capability which includes Prim+-- data ExAndPrim e = MkExAndPrim (Exception String e) (P.Prim e)+-- -- Give it a Handle instance, as per Bluefin.Compound+-- deriving (Handle) via OneWayCoercibleHandle ExAndPrim+-- deriving stock (Generic)+--+-- instance (e \<: es) => OneWayCoercible (ExAndPrim e) (ExAndPrim es) where+-- oneWayCoercibleImpl = gOneWayCoercible+--+-- -- Define a monad M containing the Prim capability+-- newtype M e es a = MkM (ReaderT (ExAndPrim e) (Eff es) a)+-- deriving newtype (Functor, Applicative, Monad)+--+-- -- Define a way of running M+-- runM ::+-- (e1 \<: es, e2 \<: es) =>+-- Exception String e1 ->+-- P.Prim e2 ->+-- M es es r ->+-- Eff es r+-- runM ex prim (MkM m) =+-- runReaderT m (MkExAndPrim (mapHandle ex) (mapHandle prim))+--+-- -- Give M a PrimMonad instance+-- instance (e \<: es) => PrimMonad (M e es) where+-- type PrimState (M e es) = P.PrimStateEff e+-- primitive f =+-- MkM (ReaderT (\\(MkExAndPrim _ prim) -> P.'primitive' prim f))+--+-- -- ghci> example+-- -- Right [\"Hello\",\"World\"]+-- example :: Either String [String]+-- example = runPureEff $ try $ \\ex -> P.'runPrim' $ \\prim -> do+-- runM ex prim $ do+-- arr <- A.newArray 2 \"Hello\"+-- A.writeArray arr 1 \"World\"+-- for [0, 1] (A.readArray arr)+-- @+module Bluefin.Prim (Prim, runPrim, PrimStateEff, primitive) where++import Bluefin.Internal.Prim
src/Bluefin/Reader.hs view
@@ -1,6 +1,12 @@+-- | This is an old interface and will be deprecated in the+-- future. You are encouraged to use "Bluefin.Capability.Ask" instead. module Bluefin.Reader ( -- | 'Reader' is Bluefin's version of the- -- "Control.Monad.Trans.Reader" monad.+ -- "Control.Monad.Trans.Reader" monad. 'local' allows you to+ -- locally override the value in the @Reader@ capability in a+ -- well-scoped way. The original value will be restored when you+ -- exit the @local@ block regardless of whether the exit was+ -- normal or via an exception . -- * Handle Reader,
src/Bluefin/State.hs view
@@ -1,10 +1,15 @@+-- | This is an old interface and will be deprecated in the+-- future. You are encouraged to use "Bluefin.Capability.Modify"+-- instead. module Bluefin.State ( -- * Handle State,+ -- * Handlers evalState, runState, withState,+ -- * Effectful operations get, put,
src/Bluefin/StateSource.hs view
@@ -1,6 +1,6 @@ module Bluefin.StateSource ( -- | A 'StateSource' allows you to allocate new- -- 'Bluefin.State.State' handles, much like 'Control.Monad.ST'+ -- t'Bluefin.State.State' handles, much like t'Control.Monad.ST' -- allows you to allocate new 'Data.STRef.STRef's. This can be -- useful when you want to avoid nested 'Bluefin.State.runState' -- (or `Bluefin.State.evalState') blocks, or you need a number
src/Bluefin/Stream.hs view
@@ -1,13 +1,20 @@+-- | This is an old interface and will be deprecated in the+-- future. You are encouraged to use "Bluefin.Capability.Yield"+-- instead. module Bluefin.Stream ( -- | 'Stream' allows you to yield values during the execution of a -- Bluefin operation. It provides similar functionality to -- Python's @yield@. The handler of the 'Stream' will either -- handle each element as soon as it is yielded (for example- -- 'forEach') or gather all yielded elements int o a list (for+ -- 'forEach') or gather all yielded elements into a list (for -- example 'yieldToList').+ --+ -- For information about prompt finalization/resource safety when+ -- using Bluefin @Stream@s, see "Bluefin.Coroutine". -- * Handle Stream,+ -- * Handlers forEach, yieldToList,@@ -20,6 +27,7 @@ catMaybes, consumeStream, streamConsume,+ -- * Effectful operations yield, inFoldable,
src/Bluefin/System/IO.hs view
@@ -1,5 +1,4 @@ -- | A safer interface to @System.IO.'System.IO.Handle'@- module Bluefin.System.IO ( -- * Handle Handle,@@ -16,7 +15,6 @@ hFlush, -- * Unsafe- unsafeWithHandle, ) where
src/Bluefin/Writer.hs view
@@ -1,3 +1,6 @@+-- | This is an old interface and will be deprecated in the+-- future. You are encouraged to use "Bluefin.Capability.Writer"+-- instead. module Bluefin.Writer ( -- | In most cases you'll probably prefer t'Bluefin.Stream.Stream' -- to @Writer@, but @Writer@ can still be useful in some cases,@@ -6,9 +9,11 @@ -- * Handle Writer,+ -- * Handlers runWriter, execWriter,+ -- * Effectful operations tell, )