packages feed

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)