bluefin-internal-0.4.3.0: src/Bluefin/Internal/GadtEffect.hs
{-# LANGUAGE DerivingVia #-}
module Bluefin.Internal.GadtEffect where
import Bluefin.Internal
( Eff,
Effects,
Handle,
HandleReader,
OneWayCoercibleHandle (..),
localHandle,
mapHandle,
oneWayCoercibleTrustMe,
useImplIn,
useImplUnder,
(:&),
(:>),
)
import Bluefin.Internal.OneWayCoercible (OneWayCoercible (oneWayCoercibleImpl), OneWayCoercibleD)
import Data.Kind (Type)
type Send :: Effect -> Effects -> Type
-- | Bring a 'Send' into scope with 'interpret'.
newtype Send f e = MkSend (EffectHandler f e)
deriving (Handle) via OneWayCoercibleHandle (Send f)
-- | A convenient type synoynm matching
-- [@effectful@](https://hackage-content.haskell.org/package/effectful-core/docs/Effectful.html#t:Effect)
-- and
-- [@polysemy@](https://hackage.haskell.org/package/polysemy/docs/Polysemy.html#t:Effect)'s
-- usages provided for people who are migrating from those libraries.
type Effect = (Type -> Type) -> Type -> Type
instance (e :> es) => OneWayCoercible (Send f e) (Send f es) where
oneWayCoercibleImpl =
oneWayCoercibleTrustMe (\(MkSend g) -> MkSend (useImplUnder . g))
-- | Send a primitive operation to the handler for interpretation.
-- This is the Bluefin analog of @effectful@'s
-- [@send@](https://hackage.haskell.org/package/effectful-core/docs/Effectful-Dispatch-Dynamic.html#v:send)
-- and @polysemy@'s
-- [@send@](https://hackage.haskell.org/package/polysemy/docs/Polysemy.html#v:send).
send ::
(e1 :> es) =>
Send f e1 ->
-- | Handle this operation using the effect handler currently in
-- scope for the @Send f@ handle.
f (Eff es) r ->
Eff es r
send (MkSend g) = useImplIn g
-- | A convenient type synonym. This is like @effectful@'s
-- [@EffectHandler@](https://hackage-content.haskell.org/package/effectful-core-2.6.1.0/docs/Effectful-Dispatch-Dynamic.html#t:EffectHandler).
-- A similar type also appears in @polysemy@ as the argument to
-- functions like
-- [@intercept@](https://hackage.haskell.org/package/polysemy-1.9.2.0/docs/Polysemy.html#v:intercept).
type EffectHandler f es =
forall e r.
f (Eff e) r ->
-- | ͘
Eff (e :& es) r
-- |
-- @
-- 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)
-- @
interpret ::
-- | Implementation of effect handler for @Send f@
EffectHandler f es ->
-- | Within this block, @send@ has the implementation given above.
(forall e. Send f e -> Eff (e :& es) r) ->
Eff es r
interpret g k = useImplIn k (MkSend g)
type GadtEffect :: ((Type -> Type) -> Type -> Type) -> Type -> Effects -> Type
newtype GadtEffect f a e = MkGadtEffect {unGadtEffect :: f (Eff e) a}
mapGadtEffect ::
(f1 (Eff e1) r1 -> f2 (Eff e2) r2) ->
GadtEffect f1 r1 e1 ->
GadtEffect f2 r2 e2
mapGadtEffect f = MkGadtEffect . f . unGadtEffect
-- |
-- @
-- 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)
-- @
oneWayCoercibleGadtEffectTrustMe ::
(e :> es) =>
(forall e' es'. (e' :> es') => f (Eff e') r -> f (Eff es') r) ->
-- | ͘
OneWayCoercibleD (GadtEffect f r e) (GadtEffect f r es)
oneWayCoercibleGadtEffectTrustMe k = oneWayCoercibleTrustMe (mapGadtEffect k)
-- | Version of 'send' for use when pattern matching in 'interpose'
--
-- @
-- 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
-- @
passthrough ::
(Handle (GadtEffect f r), e1 :> es, e2 :> es) =>
Send f e1 ->
f (Eff e2) r ->
-- | ͘
Eff es r
passthrough fc = send fc . unGadtEffect . mapHandle . MkGadtEffect
-- |
-- @
-- 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
-- @
interpose ::
(e1 :> es) =>
-- | Reimplementation of effect handler for @Send f@ in terms of the
-- the original effect handler, which is passed as the argument
(Send f es -> EffectHandler f es) ->
-- | Original effect handler
HandleReader (Send f) e1 ->
-- | Within this block, @send@ has the implementation given above.
Eff es r ->
Eff es r
interpose h hr = localHandle hr (\fcOrig -> MkSend (h fcOrig))