bluefin (empty) → 0.0.0.0
raw patch · 13 files changed
+405/−0 lines, 13 filesdep +bluefin-internal
Dependencies added: bluefin-internal
Files
- CHANGELOG.md +3/−0
- LICENSE +20/−0
- bluefin.cabal +34/−0
- src/Bluefin.hs +198/−0
- src/Bluefin/Compound.hs +16/−0
- src/Bluefin/Coroutine.hs +18/−0
- src/Bluefin/EarlyReturn.hs +15/−0
- src/Bluefin/Eff.hs +22/−0
- src/Bluefin/Exception.hs +13/−0
- src/Bluefin/IO.hs +20/−0
- src/Bluefin/Jump.hs +14/−0
- src/Bluefin/State.hs +14/−0
- src/Bluefin/Stream.hs +18/−0
+ CHANGELOG.md view
@@ -0,0 +1,3 @@+## 0.0.0.0++* Initial version
+ LICENSE view
@@ -0,0 +1,20 @@+Copyright (c) 2024 Tom Ellis++Permission is hereby granted, free of charge, to any person obtaining+a copy of this software and associated documentation files (the+"Software"), to deal in the Software without restriction, including+without limitation the rights to use, copy, modify, merge, publish,+distribute, sublicense, and/or sell copies of the Software, and to+permit persons to whom the Software is furnished to do so, subject to+the following conditions:++The above copyright notice and this permission notice shall be included+in all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ bluefin.cabal view
@@ -0,0 +1,34 @@+cabal-version: 3.0+name: bluefin+version: 0.0.0.0+license: MIT+license-file: LICENSE+author: Tom Ellis+maintainer: Tom Ellis+build-type: Simple+extra-doc-files: CHANGELOG.md+description: The Bluefin effect system+synopsis: The Bluefin effect system++common warnings+ ghc-options: -Wall++library+ import: warnings+ default-extensions:+ NoImplicitPrelude+ exposed-modules:+ Bluefin,+ Bluefin.Compound,+ Bluefin.Coroutine,+ Bluefin.EarlyReturn,+ Bluefin.Eff,+ Bluefin.Exception,+ Bluefin.Jump,+ Bluefin.IO,+ Bluefin.State,+ Bluefin.Stream,+ build-depends:+ bluefin-internal < 0.1+ hs-source-dirs: src+ default-language: Haskell2010
+ src/Bluefin.hs view
@@ -0,0 +1,198 @@+module Bluefin+ ( -- * In brief++ -- | Bluefin is an effect system which allows you, though+ -- value-level handles, to freely mix a variety of effects+ -- including+ --+ -- * "Bluefin.EarlyReturn", for early return+ -- * "Bluefin.Exception", for exceptions+ -- * "Bluefin.IO", for I/O+ -- * "Bluefin.State", for mutable state+ -- * "Bluefin.Stream", for streams++ -- * Introduction++ -- | 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+ -- 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'.+ --+ -- @+ -- -- 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+ -- -- initialize the value of the state to n+ -- 'Bluefin.State.evalState' n $ \\sn -> do+ -- n' <- 'Bluefin.State.get' sn+ -- when (n' < 10) $+ -- 'Bluefin.State.modify' sn (+ 10)+ -- get sn+ -- @+ --+ -- @+ -- >>> example1 5+ -- 15+ -- >>> example1 12+ -- 12+ -- @+ --+ -- The handle @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+ -- to have multiple effects of the same type in scope at the same+ -- time. It's easy to disambiguate them because they are distinct+ -- values! It is not simple with existing effect systems because+ -- they require the disambiguation to occur at the type level.+ -- Here is an example with two mutable @Int@ state effects in+ -- scope.+ --+ -- @+ -- -- Compare two values and add 10+ -- -- to the smaller+ -- example2 :: (Int, Int) -> (Int, Int)+ -- example2 (m, n) = 'Bluefin.Eff.runPureEff' $+ -- 'Bluefin.State.evalState' m $ \\sm -> do+ -- evalState n $ \\sn -> do+ -- do+ -- n' <- 'Bluefin.State.get' sn+ -- m' <- get sm+ --+ -- if n' < m'+ -- then 'Bluefin.State.modify' sn (+ 10)+ -- else modify sm (+ 10)+ --+ -- n' <- get sn+ -- m' <- get sm+ --+ -- pure (n', m')+ -- @+ --+ -- @+ -- >>> example2 (5, 10)+ -- (15, 10)+ -- >>> example2 (30, 3)+ -- (30, 13)+ -- @++ -- ** Effect scoping++ -- | Bluefin's use of the type system is very similar to+ -- '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.++ -- * Comparison to other effect systems++ -- ** Everything except effectful++ -- | The design of Bluefin is strongly inspired by and based on+ -- effectful. All the points in [effectful's comparison of itself+ -- to other effect+ -- systems](https://github.com/haskell-effectful/effectful?tab=readme-ov-file#motivation)+ -- apply to Bluefin too.++ -- ** effectful++ -- | The major difference between Bluefin and effectful is that in+ -- Bluefin effects are represented as value-level handles 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+ -- a well-typed implementation of something even simpler: "the+ -- functions-that-return-@IO@ pattern". The aim of the Bluefin+ -- style of value-level effect tracking is to make it even easier+ -- to mix effects, especially effects of the same type. Only time+ -- will tell which approach is preferable in practice.++ -- Haddock seems to have trouble with italic sections spanning+ -- lines :(++ -- | "/Why not just implement Bluefin as an alternative API on/+ -- /top of effectful?/"+ --+ -- It would be great to share code between the two projects! But+ -- there are two Bluefin features that I don't know to implement+ -- in terms of effectful: 'Bluefin.Bluefin.Coroutine's and+ -- 'Bluefin.Bluefin.Compound' effects.++ -- * Implementation++ -- | Bluefin has a similar implementation style to effectful.+ -- 'Bluefin.Eff.Eff' is an opaque wrapper around 'IO',+ -- 'Bluefin.State.State' is an opaque wrapper around+ -- 'Data.IORef.IORef', and 'Bluefin.Exception.throw' throws an+ -- actual @IO@ exception. 'Bluefin.Coroutine.Coroutine', which+ -- doesn't exist in effectful, is implemented simply as a+ -- function.+ --+ -- @+ -- newtype 'Bluefin.Eff.Eff' (es :: 'Bluefin.Eff.Effects') a = 'Bluefin.Internal.UnsafeMkEff' (IO a)+ -- newtype 'Bluefin.State.State' s (st :: Effects) = 'Bluefin.Internal.UnsafeMkState' (IORef s)+ -- newtype 'Bluefin.Coroutine.Coroutine' a b (s :: Effects) = 'Bluefin.Internal.UnsafeMkCoroutine' (a -> IO b)+ -- @+ --+ -- The type parameters of kind '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+ -- 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+ -- @IO@ action using 'System.IO.Unsafe.unsafePerformIO', which is+ -- the approach taken to implement 'Bluefin.Eff.runPureEff'.++ -- * Tips++ -- | * Use @NoMonoLocalBinds@ and @NoMonomorphismRestriction@ for+ -- better type inference.+ --+ -- * Writing a handler often requires an explicit type signature.++ -- * Example++ -- |+ -- @+ -- 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+ -- for_ is $ \\i -> do+ -- case compare i 0 of+ -- GT -> 'Bluefin.State.modify' positives (+ 1)+ -- EQ -> throw ex ()+ -- LT -> modify negatives (+ 1)+ --+ -- p <- 'Bluefin.State.get' positives+ -- n <- get negatives+ --+ -- pure $+ -- "Positives: "+ -- ++ show p+ -- ++ ", negatives "+ -- ++ show n+ --+ -- case r of+ -- Right r' -> pure r'+ -- Left () -> do+ -- p <- get positives+ -- pure $+ -- "We saw a zero, but before that there were "+ -- ++ show p+ -- ++ " positives"+ -- @+ )+where
+ src/Bluefin/Compound.hs view
@@ -0,0 +1,16 @@+module Bluefin.Compound+ ( -- | @Compound@ allows combining two effects into one, for+ -- encapsulation. It is not documented yet.++ -- * Handle+ Compound,++ -- * Handler+ runCompound,++ -- * Effectful operations+ withCompound,+ )+where++import Bluefin.Internal
+ src/Bluefin/Coroutine.hs view
@@ -0,0 +1,18 @@+module Bluefin.Coroutine+ ( -- | @Coroutine@ allows to yield values and receive results back.+ -- It is not documented yet. You might want to start with+ -- "Bluefin.Stream", which is the most common way to use+ -- coroutines.++ -- * Handle+ Coroutine,++ -- * Handlers+ forEach,++ -- * Effectful operations+ yieldCoroutine,+ )+where++import Bluefin.Internal
+ src/Bluefin/EarlyReturn.hs view
@@ -0,0 +1,15 @@+module Bluefin.EarlyReturn+ ( -- | Early return 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.++ -- * Handle+ EarlyReturn,+ -- * Handlers+ withEarlyReturn,+ -- * Effectful operations+ returnEarly,+ )+where++import Bluefin.Internal
+ src/Bluefin/Eff.hs view
@@ -0,0 +1,22 @@+module Bluefin.Eff+ ( -- * 'Eff' monad+ Eff,+ -- * Run an 'Eff'+ runPureEff,+ runEff,+ -- * 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,+ (:>),+ (:&),+ )+where++import Bluefin.Internal
+ src/Bluefin/Exception.hs view
@@ -0,0 +1,13 @@+module Bluefin.Exception+ ( -- * Handle+ Exception,+ -- * Handlers+ try,+ handle,+ catch,+ -- * Effectful operations+ throw,+ )+where++import Bluefin.Internal
+ src/Bluefin/IO.hs view
@@ -0,0 +1,20 @@+module Bluefin.IO+ ( -- | You can run 'IO' operations inside 'Eff'.++ -- * Handle+ IOE,+ -- * Handlers+ runEff,+ -- * Effectful operations+ effIO,+ -- * IO type classes+ withMonadIO,+ withEffToIO,+ -- ** @EffReader@+ EffReader,+ effReader,+ runEffReader,+ )+where++import Bluefin.Internal
+ src/Bluefin/Jump.hs view
@@ -0,0 +1,14 @@+module Bluefin.Jump+ ( -- | Jump allows you to jump back to a previously-set location.+ -- It is not documented yet.++ -- * Handle+ Jump,+ -- * Handlers+ withJump,+ -- * Effectful operations+ jumpTo,+ )+where++import Bluefin.Internal
+ src/Bluefin/State.hs view
@@ -0,0 +1,14 @@+module Bluefin.State+ ( -- * Handle+ State,+ -- * Handlers+ evalState,+ runState,+ -- * Effectful operations+ get,+ put,+ modify,+ )+where++import Bluefin.Internal
+ src/Bluefin/Stream.hs view
@@ -0,0 +1,18 @@+module Bluefin.Stream+ ( -- * Handle+ Stream,+ -- * Handlers+ forEach,+ yieldToList,+ yieldToReverseList,+ enumerate,+ enumerateFrom,+ mapMaybe,+ catMaybes,+ -- * Effectful operations+ yield,+ inFoldable,+ )+where++import Bluefin.Internal