bluefin 0.4.2.0 → 0.4.3.0
raw patch · 4 files changed
+273/−5 lines, 4 filesdep ~bluefin-internalPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependency ranges changed: bluefin-internal
API changes (from Hackage documentation)
+ Bluefin.GadtEffect: data GadtEffect (f :: Type -> Type -> Type -> Type) a (e :: Effects)
+ Bluefin.GadtEffect: data Send (f :: Effect) (e :: Effects)
+ Bluefin.GadtEffect: interpose :: forall (e1 :: Effects) (es :: Effects) (f :: Effect) r. e1 :> es => (Send f es -> EffectHandler f es) -> HandleReader (Send f) e1 -> Eff es r -> Eff es r
+ Bluefin.GadtEffect: interpret :: forall (f :: (Type -> Type) -> Type -> Type) (es :: Effects) r. EffectHandler f es -> (forall (e :: Effects). () => Send f e -> Eff (e :& es) r) -> Eff es r
+ Bluefin.GadtEffect: oneWayCoercibleGadtEffectTrustMe :: forall (e :: Effects) (es :: Effects) f r. e :> es => (forall (e' :: Effects) (es' :: Effects). e' :> es' => f (Eff e') r -> f (Eff es') r) -> OneWayCoercibleD (GadtEffect f r e) (GadtEffect f r es)
+ Bluefin.GadtEffect: passthrough :: forall f r (e1 :: Effects) (es :: Effects) (e2 :: Effects). (Handle (GadtEffect f r), e1 :> es, e2 :> es) => Send f e1 -> f (Eff e2) r -> Eff es r
+ Bluefin.GadtEffect: send :: forall (e1 :: Effects) (es :: Effects) f r. e1 :> es => Send f e1 -> f (Eff es) r -> Eff es r
+ Bluefin.GadtEffect: type Effect = Type -> Type -> Type -> Type
+ Bluefin.GadtEffect: type EffectHandler (f :: Type -> Type -> Type -> Type) (es :: Effects) = forall (e :: Effects) r. () => f Eff e r -> Eff e :& es r
+ Bluefin.HandleReader: asksHandle :: forall (e1 :: Effects) (es :: Effects) h r. (e1 :> es, Handle h) => HandleReader h e1 -> (forall (e :: Effects). () => h e -> Eff (e :& es) r) -> Eff es r
- Bluefin.Prim: data PrimStateEff (es :: k)
+ Bluefin.Prim: data PrimStateEff (es :: Effects)
Files
- CHANGELOG.md +11/−0
- bluefin.cabal +3/−2
- src/Bluefin/GadtEffect.hs +252/−0
- src/Bluefin/HandleReader.hs +7/−3
CHANGELOG.md view
@@ -1,3 +1,14 @@+# 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`
bluefin.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: bluefin-version: 0.4.2.0+version: 0.4.3.0 license: MIT license-file: LICENSE author: Tom Ellis@@ -31,6 +31,7 @@ Bluefin.Eff, Bluefin.Exception, Bluefin.Exception.GeneralBracket,+ Bluefin.GadtEffect, Bluefin.HandleReader, Bluefin.IO, Bluefin.Jump,@@ -44,6 +45,6 @@ Bluefin.System.IO, Bluefin.Writer, build-depends:- bluefin-internal >= 0.4.1.0 && < 0.5+ bluefin-internal >= 0.4.3.0 && < 0.5 hs-source-dirs: src default-language: Haskell2010
+ src/Bluefin/GadtEffect.hs view
@@ -0,0 +1,252 @@+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, it @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
@@ -6,9 +6,12 @@ -- exception. -- -- @HandleReader@ supports functionality similiar to @effectful@'s--- 'Effectful.Dispatch.Dynamic.interpose' and @polysemy@'s--- 'Polysemy.intercept', that is, locally augmenting an effect with--- new behaviors.+-- [@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.FunctorCoroutine.'Bluefin.FunctorCoroutine.interpose`@. module Bluefin.HandleReader ( -- * Handle@@ -19,6 +22,7 @@ -- * Effectful operations askHandle,+ asksHandle, localHandle, ) where