bluefin-algae-0.1.0.2: src/Bluefin/Algae/DynExn.hs
{-# LANGUAGE
BangPatterns,
DataKinds,
DeriveAnyClass,
GADTs,
RankNTypes,
ScopedTypeVariables,
StandaloneKindSignatures,
TypeOperators #-}
-- | = Algebraic effects and named handlers
--
-- Variant of "Bluefin.Algae" using dynamic exceptions to cancel continuations.
module Bluefin.Algae.DynExn
( AEffect
, HandlerBody
, Handler
, handle
, call
, continue
, discontinue
, discontinueIO
, cancel
, CancelContinuation(..)
) where
import Control.Exception (Exception)
import Data.Kind (Type)
import Data.Functor (void)
import Bluefin.Internal (Eff, Effects, type (:&), type (:>), IOE)
import Bluefin.Algae.DelCont (PromptTag, Continuation, reset, shift0, resume, continue)
import Bluefin.Exception.Dynamic
import Bluefin.Algae (AEffect)
-- | Interpretation of an algebraic effect @f@: a function to handle the operations of @f@
-- with cancellable continuations.
type HandlerBody :: Effects -> AEffect -> Effects -> Type -> Type
type HandlerBody ex f ss a = (forall x ss0. ex :> ss0 => f x -> Continuation ss0 ss x a -> Eff ss a)
-- | Handler to call operations of the effect @f@ with cancellable continuations.
type Handler :: Effects -> AEffect -> Effects -> Type
data Handler ex f s where
MkHandler :: !(PromptTag ss a s) -> HandlerBody ex f ss a -> Handler ex f s
-- | Handle operations of @f@ with cancellable continuations.
--
-- The handle for exceptions (first argument) is only there to guide type inference.
-- it can be either 'IOE' or 'DynExn'.
handle ::
h ex ->
HandlerBody ex f ss a ->
(forall s. Handler ex f s -> Eff (s :& ss) a) ->
Eff ss a
handle _ h act = reset (\p -> act (MkHandler p h))
-- | Call an operation of @f@ with cancellable continuations.
call :: (ex :> es, s :> es) => Handler ex f s -> f a -> Eff es a
call (MkHandler p h) op = shift0 p (\k -> h op k)
-- | Resume by throwing a dynamic exception.
--
-- Note that different outcomes are possible depending on your handled computation.
-- Be sure to handle them appropriately.
--
-- - A common situation is that the continuation will rethrow the initial exception,
-- then you can just catch it (or use 'cancel').
-- - The continuation may throw a different exception, so you should be
-- careful to catch the right exception.
-- - The continuation may also catch your exception and terminate normally
-- with a result of type @a@.
discontinue :: (Exception e, ex :> es0) => DynExn ex -> Continuation es0 es b a -> e -> Eff es a
discontinue ex k e = resume k (throw ex e)
-- | Specialization of 'discontinue' to 'IOE'.
discontinueIO :: (Exception e, io :> es0) => IOE io -> Continuation es0 es b a -> e -> Eff es a
discontinueIO io = discontinue (ioeToDynExn io)
-- | 'discontinue' a continuation with the v'CancelContinuation' exception and catch it when it
-- is re-thrown by the continuation.
--
-- The continuation SHOULD re-throw v'CancelContinuation' if it catches it.
cancel :: (ex :> es0, ex :> es) => DynExn ex -> Continuation es0 es b a -> Eff es ()
cancel ex k = catch ex (void (discontinue ex k CancelContinuation)) (\CancelContinuation -> pure ())
-- | Exception thrown by 'cancel'.
data CancelContinuation = CancelContinuation
deriving (Show, Exception)