packages feed

bluefin 0.0.3.0 → 0.0.4.0

raw patch · 4 files changed

+505/−21 lines, 4 filesdep ~bluefin-internalPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: bluefin-internal

API changes (from Hackage documentation)

+ Bluefin.Compound: class () => Handle (h :: Effects -> Type)
+ Bluefin.Compound: mapHandle :: forall (e :: Effects) (es :: Effects). (Handle h, e :> es) => h e -> h es
+ Bluefin.Compound: useImpl :: forall (e :: Effects) (es :: Effects) r. e :> es => Eff e r -> Eff es r
+ Bluefin.Compound: useImplIn :: forall (e :: Effects) (es :: Effects) t r. e :> es => (t -> Eff (es :& e) r) -> t -> Eff es r

Files

CHANGELOG.md view
@@ -1,3 +1,15 @@+## 0.0.4.0++* Add documentation and functions to `Bluefin.Compound`++## 0.0.3.0++* Add `Bluefin.Reader`++## 0.0.2.0++* Add `Bluefin.StateSource`+ ## 0.0.1.0  * Add `Bluefin.Writer`
bluefin.cabal view
@@ -1,6 +1,6 @@ cabal-version:      3.0 name:               bluefin-version:            0.0.3.0+version:            0.0.4.0 license:            MIT license-file:       LICENSE author:             Tom Ellis@@ -34,6 +34,6 @@       Bluefin.Stream,       Bluefin.Writer,     build-depends:-      bluefin-internal >= 0.0.3 && < 0.1+      bluefin-internal >= 0.0.4 && < 0.1     hs-source-dirs:   src     default-language: Haskell2010
src/Bluefin.hs view
@@ -1,8 +1,8 @@ module Bluefin   ( -- * In brief -    -- | Bluefin is an effect system which allows you, though-    -- value-level handles, to freely mix a variety of effects+    -- | Bluefin is an effect system which allows you to freely mix a+    -- variety of effects, accessed though value-level handles,     -- including     --     --  * "Bluefin.EarlyReturn", for early return@@ -88,25 +88,26 @@     -- ** 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 handle 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     -- anymore.      -- ** Type signatures -    -- | Bluefin type signatures follow a common pattern which looks-    -- like+    -- | The type signatures of Bluefin functions follow a common+    -- pattern which looks like     --     -- @     -- (e1 :> es, ...) -> \<Handle\> e1 -> ... -> Eff es r     -- @     ---    ---    -- Consider the example below, @incrementReadLine@, which reads-    -- integers from standard input and 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.+    -- Here @\<Handle\>@ could be, for example, @State Int@,+    -- @Exception String@ or @IOE@.  Consider the example below,+    -- @incrementReadLine@, which reads integers from standard input+    -- and 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,@@ -144,7 +145,7 @@     -- incrementReadLine state exception io = do     --   'Bluefin.Jump.withJump' $ \\break -> 'Control.Monad.forever' $ do     --     line <- 'Bluefin.IO.effIO' io getLine-    --     i <- case 'Text.Maybe.readMaybe' line of+    --     i <- case 'Text.Read.readMaybe' line of     --       Nothing ->     --         'Bluefin.Exception.throw' exception ("Couldn't read: " ++ line)     --       Just i ->@@ -160,9 +161,20 @@     -- 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.+    -- '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+    -- remaining effects, @es@.  (The signatures for @runEff@ and+    -- @runPureEff@ are slightly different because they remove all+    -- effects.)+    --+    -- @     -- runIncrementReadLine :: IO (Either String Int)     -- runIncrementReadLine = 'Bluefin.Eff.runEff' $ \\io -> do     --   'Bluefin.Exception.try' $ \\exception -> do@@ -255,6 +267,10 @@     -- default after adding inferred type signatures.)     --     -- * Writing a handler often requires an explicit type signature.++    -- * Creating your own effects++    -- | See "Bluefin.Compound".      -- * Example 
src/Bluefin/Compound.hs view
@@ -1,14 +1,470 @@ module Bluefin.Compound-  ( -- | @Compound@ allows combining two effects into one, for-    -- encapsulation.  It is not documented yet.+  ( -- * Creating your own effects -    -- * Handle-    Compound,+    -- ** Wrap a single effect -    -- * Handler-    runCompound,+    -- | Because in Bluefin everything happens at the value level,+    -- 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 @State@+    -- handle in a newtype:+    --+    -- @+    -- newtype Counter1 e = MkCounter1 (State Int e)+    --+    -- incCounter1 :: (e :> es) => Counter1 e -> Eff es ()+    -- incCounter1 (MkCounter1 st) = modify st (+ 1)+    --+    -- runCounter1 ::+    --   (forall e. Counter1 e -> Eff (e :& es) r) ->+    --   Eff es Int+    -- runCounter1 k =+    --   evalState 0 $ \\st -> do+    --     _ <- k (MkCounter1 st)+    --     get st+    -- @+    --+    -- Running the handler tells me the number of times I incremented+    -- the counter.+    --+    -- @+    -- exampleCounter1 :: Int+    -- exampleCounter1 = runPureEff $ runCounter1 $ \\c ->+    --   incCounter1 c+    --   incCounter1 c+    --   incCounter1 c+    -- @+    --+    -- @+    -- >>> exampeleCounter1+    -- 3+    -- @ -    -- * Effectful operations+    -- ** Wrap multiple effects, first attempt++    -- | If we want to wrap multiple effects then we can use the+    -- 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 @State@ and @Exception@ handle+    -- within it.  That allows us to increment the counter and throw+    -- an exception when we hit a limit.+    --+    -- @+    -- data Counter2 e1 e2 = MkCounter2 (State Int e1) (Exception () e2)+    --+    -- incCounter2 :: (e1 :> es, e2 :> es) => Counter2 e1 e2 -> Eff es ()+    -- incCounter2 (MkCounter2 st ex) = do+    --   count <- get st+    --   when (count >= 10) $+    --     throw ex ()+    --   put st (count + 1)+    --+    -- runCounter2 ::+    --   (forall e1 e2. Counter2 e1 e2 -> Eff (e2 :& e1 :& es) r) ->+    --   Eff es Int+    -- runCounter2 k =+    --   evalState 0 $ \\st -> do+    --     _ \<- try $ \\ex -> do+    --       k (MkCounter2 st ex)+    --     get st+    -- @+    --+    -- We can see that attempting to increment the counter fovever+    -- bails out when we reach the limit.+    --+    -- @+    -- exampleCounter2 :: Int+    -- exampleCounter2 = runPureEff $ runCounter2 $ \\c ->+    --   forever $+    --     incCounter2 c+    -- @+    --+    -- @+    -- >>> exampleCounter2+    -- 10+    -- @+    --+    -- The flaw of this approach is that you expose one effect+    -- parameter for each handle in the data type.  That's rather+    -- cumbersome!  We can do better.++    -- ** Wrap multiple effects, a better approach++    -- | We can avoid exposing multiple effect parameters and just+    -- 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.+    --+    -- @+    -- data Counter3 e = MkCounter3 (State Int e) (Exception () e)+    --+    -- incCounter3 :: (e :> es) => Counter3 e -> Eff es ()+    -- incCounter3 (MkCounter3 st ex) = do+    --   count <- get st+    --   when (count >= 10) $+    --     throw ex ()+    --   put st (count + 1)+    --+    -- runCounter3 ::+    --   (forall e. Counter3 e -> Eff (e :& es) r) ->+    --   Eff es Int+    -- runCounter3 k =+    --   evalState 0 $ \\st -> do+    --     _ \<- try $ \\ex -> do+    --       useImplIn k (MkCounter3 (mapHandle st) (mapHandle ex))+    --     get st+    -- @+    --+    -- The example works as before:+    --+    -- @+    -- exampleCounter3 :: Int+    -- exampleCounter3 = runPureEff $ runCounter3 $ \\c ->+    --   forever $+    --     incCounter3 c+    -- @+    --+    -- @+    -- >>> exampleCounter3+    -- 10+    -- @++    -- ** Wrap multiple effects, don't handle them all++    -- | So far our handlers have handled all the effects that are+    -- found within our compound effect. We don't have to do that+    -- though: we can leave some of the effects unhandled to be+    -- handled by a different handler at a higher level.  Let's extend+    -- our example with a @Stream@ effect.  Whenever we ask to+    -- increment the counter, and it is currently an even number, then+    -- we yield a message about that.  Additionally, there's a new+    -- operation @getCounter4@ which allows us to yield a message+    -- whilst returning the value of the counter.+    --+    -- @+    -- data Counter4 e+    --   = MkCounter4 (State Int e) (Exception () e) (Stream String e)+    --+    -- incCounter4 :: (e :> es) => Counter4 e -> Eff es ()+    -- incCounter4 (MkCounter4 st ex y) = do+    --   count <- get st+    --+    --   when (even count) $+    --     yield y "Count was even"+    --+    --   when (count >= 10) $+    --     throw ex ()+    --+    --   put st (count + 1)+    --+    -- getCounter4 :: (e :> es) => Counter4 e -> String -> Eff es Int+    -- getCounter4 (MkCounter4 st _ y) msg = do+    --   yield y msg+    --   get st+    --+    -- runCounter4 ::+    --   (e1 :> es) =>+    --   Stream String e1 ->+    --   (forall e. Counter4 e -> Eff (e :& es) r) ->+    --   Eff es Int+    -- runCounter4 y k =+    --   evalState 0 $ \\st -> do+    --     _ \<- try $ \\ex -> do+    --       useImplIn k (MkCounter4 (mapHandle st) (mapHandle ex) (mapHandle y))+    --     get st+    -- @+    --+    -- @+    -- exampleCounter4 :: ([String], Int)+    -- exampleCounter4 = runPureEff $ yieldToList $ \\y -> do+    --   runCounter4 y $ \\c -> do+    --     incCounter4 c+    --     incCounter4 c+    --     n <- getCounter4 c "I'm getting the counter"+    --     when (n == 2) $+    --       yield y "n was 2, as expected"+    -- @+    --+    -- @+    -- >>> exampleCounter4+    -- (["Count was even","I'm getting the counter","n was 2, as expected"],2)+    -- @++    -- ** Dynamic effects++    -- | So far we've looked at "concrete" compound effects, that is,+    -- 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+    -- 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 @useImpl@.+    --+    -- @+    -- data Counter5 e = MkCounter5+    --   { incCounter5Impl :: Eff e (),+    --     getCounter5Impl :: String -> Eff e Int+    --   }+    --+    -- incCounter5 :: (e :> es) => Counter5 e -> Eff es ()+    -- incCounter5 e = useImpl (incCounter5Impl e)+    --+    -- getCounter5 :: (e :> es) => Counter5 e -> String -> Eff es Int+    -- getCounter5 e msg = useImpl (getCounter5Impl e msg)+    --+    -- runCounter5 ::+    --   (e1 :> es) =>+    --   Stream String e1 ->+    --   (forall e. Counter5 e -> Eff (e :& es) r) ->+    --   Eff es Int+    -- runCounter5 y k =+    --   evalState 0 $ \\st -> do+    --     _ \<- try $ \\ex -> do+    --       useImplIn+    --         k+    --         ( MkCounter5+    --             { incCounter5Impl = do+    --                 count <- get st+    --+    --                 when (even count) $+    --                   yield y "Count was even"+    --+    --                 when (count >= 10) $+    --                   throw ex ()+    --+    --                 put st (count + 1),+    --               getCounter5Impl = \\msg -> do+    --                 yield y msg+    --                 get st+    --             }+    --         )+    --     get st+    -- @+    --+    -- The result is exactly the same as before+    --+    -- @+    -- exampleCounter5 :: ([String], Int)+    -- exampleCounter5 = runPureEff $ yieldToList $ \\y -> do+    --   runCounter5 y $ \\c -> do+    --     incCounter5 c+    --     incCounter5 c+    --     n <- getCounter5 c "I'm getting the counter"+    --     when (n == 2) $+    --       yield y "n was 2, as expected"+    -- @+    --+    -- @+    -- >>> exampleCounter5+    -- (["Count was even","I'm getting the counter","n was 2, as expected"],2)+    -- @++    -- ** Combining concrete and dynamic effects++    -- | 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 @State@ and @Stream@ effects.+    --+    -- @+    -- data Counter6 e = MkCounter6+    --   { incCounter6Impl :: Eff e (),+    --     counter6State :: State Int e,+    --     counter6Stream :: Stream String e+    --   }+    --+    -- incCounter6 :: (e :> es) => Counter6 e -> Eff es ()+    -- incCounter6 e = useImpl (incCounter6Impl e)+    --+    -- getCounter6 :: (e :> es) => Counter6 e -> String -> Eff es Int+    -- getCounter6 (MkCounter6 _ st y) msg = do+    --   yield y msg+    --   get st+    --+    -- runCounter6 ::+    --   (e1 :> es) =>+    --   Stream String e1 ->+    --   (forall e. Counter6 e -> Eff (e :& es) r) ->+    --   Eff es Int+    -- runCounter6 y k =+    --   evalState 0 $ \\st -> do+    --     _ \<- try $ \\ex -> do+    --       useImplIn+    --         k+    --         ( MkCounter6+    --             { incCounter6Impl = do+    --                 count <- get st+    --+    --                 when (even count) $+    --                   yield y "Count was even"+    --+    --                 when (count >= 10) $+    --                   throw ex ()+    --+    --                 put st (count + 1),+    --               counter6State = mapHandle st,+    --               counter6Stream = mapHandle y+    --             }+    --         )+    --     get st+    -- @+    --+    -- Naturally, the result is the same.+    --+    -- @+    -- exampleCounter6 :: ([String], Int)+    -- exampleCounter6 = runPureEff $ yieldToList $ \\y -> do+    --   runCounter6 y $ \\c -> do+    --     incCounter6 c+    --     incCounter6 c+    --     n <- getCounter6 c "I'm getting the counter"+    --     when (n == 2) $+    --       yield y "n was 2, as expected"+    -- @+    --+    -- @+    -- >>> exampleCounter6+    -- (["Count was even","I'm getting the counter","n was 2, as expected"],2)+    -- @++    -- ** A dynamic file system effect++    -- | The @effectful@ library has [an example of a dynamic effect+    -- for basic file system+    -- access](https://hackage.haskell.org/package/effectful-core-2.2.1.0/docs/Effectful-Dispatch-Dynamic.html#g:2).+    -- This is what it looks like in Bluefin.  We start by defining a+    -- record of effectful operations.+    --+    -- @+    -- data FileSystem es = MkFileSystem+    --   { readFileImpl :: FilePath -> Eff es String,+    --     writeFileImpl :: FilePath -> String -> Eff es ()+    --   }+    --+    -- readFile :: (e :> es) => FileSystem e -> FilePath -> Eff es String+    -- readFile fs filepath = useImpl (readFileImpl fs filepath)+    --+    -- writeFile :: (e :> es) => FileSystem e -> FilePath -> String -> Eff es ()+    -- writeFile fs filepath contents = useImpl (writeFileImpl fs filepath contents)+    -- @+    --+    -- We can make a pure handler that simulates reading and writing+    -- to a file system by storing file contents in an association+    -- list.+    --+    -- @+    -- runFileSystemPure ::+    --   (e1 :> es) =>+    --   Exception String e1 ->+    --   [(FilePath, String)] ->+    --   (forall e2. FileSystem e2 -> Eff (e2 :& es) r) ->+    --   Eff es r+    -- runFileSystemPure ex fs0 k =+    --   evalState fs0 $ \\fs ->+    --     useImplIn+    --       k+    --       MkFileSystem+    --         { readFileImpl = \\filepath -> do+    --             fs' <- get fs+    --             case lookup filepath fs' of+    --               Nothing ->+    --                 throw ex ("File not found: " <> filepath)+    --               Just s -> pure s,+    --           writeFileImpl = \\filepath contents ->+    --             modify fs ((filepath, contents) :)+    --         }+    -- @+    --+    -- Or we can make a handler that actually performs IO operations+    -- against a real file system.+    --+    -- @+    -- runFileSystemIO ::+    --   forall e1 e2 es r.+    --   (e1 :> es, e2 :> es) =>+    --   Exception String e1 ->+    --   IOE e2 ->+    --   (forall e. FileSystem e -> Eff (e :& es) r) ->+    --   Eff es r+    -- runFileSystemIO ex io k =+    --   useImplIn+    --     k+    --     MkFileSystem+    --       { readFileImpl =+    --           adapt . Prelude.readFile,+    --         writeFileImpl =+    --           \\filepath -> adapt . Prelude.writeFile filepath+    --       }+    --   where+    --     adapt :: (e1 :> ess, e2 :> ess) => IO a -> Eff ess a+    --     adapt m =+    --       effIO io (Control.Exception.try @IOException m) >>= \\case+    --         Left e -> throw ex (show e)+    --         Right r -> pure r+    -- @+    --+    -- We can use the @FileSystem@ effect to define an action which+    -- does some file system operations.+    --+    -- @+    -- action :: (e :> es) => FileSystem e -> Eff es String+    -- action fs = do+    --   file <- readFile fs "\/dev\/null"+    --   when (length file == 0) $ do+    --     writeFile fs "\/tmp\/bluefin" "Hello!"+    --   readFile fs "\/tmp\/doesn't exist"+    -- @+    --+    -- and we can run it purely, against a simulated file system+    --+    -- @+    -- exampleRunFileSystemPure :: Either String String+    -- exampleRunFileSystemPure = runPureEff $ try $ \\ex ->+    --   runFileSystemPure ex [("\/dev\/null", "")] action+    -- @+    --+    -- @+    -- >>> exampleRunFileSystemPure+    -- Left "File not found: \/tmp\/doesn't exist"+    -- @+    --+    -- or against the real file system.+    --+    -- @+    -- exampleRunFileSystemIO :: IO (Either String String)+    -- exampleRunFileSystemIO = runEff $ \\io -> try $ \\ex ->+    --   runFileSystemIO ex io action+    -- @+    --+    -- @+    -- >>> exampleRunFileSystemIO+    -- Left "\/tmp\/doesn't exist: openFile: does not exist (No such file or directory)"+    -- \$ cat \/tmp\/bluefin+    -- Hello!+    -- @++    -- * Functions for making compound effects++    Handle (mapHandle),+    useImpl,+    useImplIn,++    -- * Deprecated++    -- | Do not use.  Will be removed in a future version.++    Compound,+    runCompound,     withCompound,   ) where