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