module Bluefin
  ( -- * In brief

    -- | 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.IO", for I/O
    --  * "Bluefin.State", for mutable state
    --  * "Bluefin.Stream", for streams
    --
    -- and to create your own effects in terms of existing ones
    -- ("Bluefin.Compound").
    -- Bluefin effects are accessed explicitly through
    -- value-level handles.

    -- * 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 is simple to disambiguate them, because they are
    -- distinct values!  By contrast, existing effect systems require
    -- the disambiguation to occur at the type level, which imposes
    -- challenges.
    --
    -- Here is a Bluefin 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)
    -- @

    -- ** Exception handles

    -- | Bluefin exceptions are accessed through
    -- 'Bluefin.Exception.Exception' handles.  An @Exception@ handle
    -- is introduced by a handler, such as 'Bluefin.Exception.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
    -- be handled – it is not possible to have an unhandled Bluefin
    -- exception.  Secondly, a Bluefin exception can be handled in
    -- 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'.
    --
    -- @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
    -- 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
    --       for_ [1..n] $ \\i -> do
    --          soFar <- 'Bluefin.State.get' total
    --          when (soFar > 20) $ do
    --            'Bluefin.Exception.throw' ex ("Became too big: " ++ show soFar)
    --          'Bluefin.State.put' total (soFar + i)
    --
    --       'Bluefin.State.get' total
    -- @
    --
    -- @
    -- >>> example3 4
    -- Right 10
    -- >>> example3 10
    -- Left "Became too big: 21"
    -- @

    -- ** 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.

    -- ** Type signatures

    -- | The type signatures of Bluefin functions follow a common
    -- pattern which looks like
    --
    -- @
    -- (e1 :> es, ...) -> \<Handle\> e1 -> ... -> Eff es r
    -- @
    --
    -- Here @\<Handle\>@ could be, for example, @State Int@,
    -- @Exception 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,
    -- 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
    -- polymorphic.
    --
    -- Secondly, let's look at the return value, @Eff es ()@.  This
    -- means the computation is performed in the t'Bluefin.Eff.Eff'
    -- monad and the resulting value produced is of type @()@.  @Eff@
    -- is tagged with the effect type @es@, which is also always kept
    -- polymorphic.
    --
    -- 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
    -- 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
    -- actually used anywhere in the computation.
    --
    -- GHC and editor tools like HLS do a good job of inferring these
    -- type signatures.
    --
    -- @
    -- incrementReadLine ::
    --   (e1 :> es, e2 :> es, e3 :> es) =>
    --   State Int e1  ->
    --   Exception String e2  ->
    --   IOE e3 ->
    --   Eff es ()
    -- incrementReadLine state exception io = do
    --   'Bluefin.Jump.withJump' $ \\break -> 'Control.Monad.forever' $ do
    --     line <- 'Bluefin.IO.effIO' io getLine
    --     i <- case 'Text.Read.readMaybe' line of
    --       Nothing ->
    --         'Bluefin.Exception.throw' exception ("Couldn't read: " ++ line)
    --       Just i ->
    --         pure i
    --
    --     when (i == 0) $
    --       'Bluefin.Jump.jumpTo' break
    --
    --     'Bluefin.State.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
    -- 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
    -- '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
    --       incrementReadLine state exception io
    --     pure r
    --
    -- >>> runIncrementReadLine
    -- 1
    -- 2
    -- 3
    -- 0
    -- Right 6
    -- >>>> runIncrementReadLine
    -- 1
    -- 2
    -- 3
    -- Hello
    -- Left "Couldn't read: Hello"
    -- @

    -- * 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
    -- [Handle
    -- pattern](https://jaspervdj.be/posts/2018-03-08-handle-pattern.html).
    -- 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
    --  I don't know to implement Bluefin's "Bluefin.Compound" effects
    --  in effectful.

    -- * Implementation

    -- | 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
    -- 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)
    -- @
    --
    -- 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
    -- 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'.
    -- Consequently, it is impossible for a pure value retured from
    -- `runPureEff` to access any Bluefin internal state or throw a
    -- Bluefin internal exception.

    -- * Tips

    -- | * Use @NoMonoLocalBinds@ and @NoMonomorphismRestriction@ for
    -- better type inference.  (You can always change back to the
    -- default after adding inferred type signatures.)
    --
    -- * Writing a handler often requires an explicit type signature.

    -- * Creating your own effects

    -- | See "Bluefin.Compound".

    -- * 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