packages feed

bluefin-algae-0.1.0.0: src/Bluefin/Exception/Dynamic.hs

{-# LANGUAGE
  KindSignatures,
  RankNTypes,
  ScopedTypeVariables,
  TypeOperators #-}
-- | = Dynamic exceptions
--
-- This is the vanilla exception mechanism from @IO@.
-- Use this module to handle exceptions from external (non-bluefin) APIs.
--
-- Another motivation is to serve as a principled (experimental) framework
-- for resource management with 'bracket'.
--
-- The core Bluefin API exposes a 'Bluefin.Eff.bracket' in "Bluefin.Eff"
-- which (intentionally) weakens the scoping of scoped exceptions in
-- "Bluefin.Exception".
--
-- This module is an experiment for a world where
--
-- - scoped exceptions are truly scoped (unlike "Bluefin.Exception");
-- - the capability to catch and throw dynamic exceptions is explicit
--   (unlike 'Bluefin.Eff.bracket' in "Bluefin.Eff").
module Bluefin.Exception.Dynamic
  ( DynExn
  , runDynExn
  , ioeToDynExn
  , throw
  , catch
  , bracket
  , finally
  , onException
  , throwIO
  , catchIO
  ) where

import qualified Control.Exception as E
import qualified Bluefin.Internal as B
import Bluefin.Eff (Eff, Effects, type (:>))
import Bluefin.IO (IOE)

-- | Capability to catch and throw dynamic exceptions.
data DynExn (ex :: Effects) = UnsafeDynExn

-- | Run a computation with only access to dynamic exceptions.
runDynExn :: (forall ex. DynExn ex -> Eff ex a) -> a
runDynExn f = B.runPureEff (f UnsafeDynExn)

-- | Refine an 'IOE' capability to a 'DynExn'.
ioeToDynExn :: IOE io -> DynExn io
ioeToDynExn _ = UnsafeDynExn

-- | Throw an exception.
throw :: (E.Exception e, ex :> es) => DynExn ex -> e -> Eff es a
throw _ e = B.UnsafeMkEff (E.throwIO e)

-- | Catch an exception.
catch :: (E.Exception e, ex :> es) => DynExn ex -> Eff es a -> (e -> Eff es a) -> Eff es a
catch _ m h = B.UnsafeMkEff (E.catch (B.unsafeUnEff m) (B.unsafeUnEff . h))

-- | @'bracket' ex acquire release run@: @acquire@ a resource, @run@ a computation depending on it,
-- and finally @relase@ the resource even if @run@ threw an exception.
bracket :: ex :> es => DynExn ex -> Eff es a -> (a -> Eff es ()) -> (a -> Eff es b) -> Eff es b
bracket ex acquire release run = do
  a <- acquire
  finally ex (run a) (release a)

-- | @'finally' ex run cleanup@: @run@ a computation, then @cleanup@ even if
-- @run@ threw an exception.
finally :: ex :> es => DynExn ex -> Eff es a -> Eff es () -> Eff es a
finally ex run cleanup =
  onException ex run cleanup   -- if run throws an exception, then only this cleanup will run
    <* cleanup                 -- if run does not throw, then only this cleanup will run

-- | @'onException' ex run cleanup@: @run@ a computation, and if an exception is thrown,
-- @cleanup@, then rethrow the exception.
onException :: ex :> es => DynExn ex -> Eff es a -> Eff es () -> Eff es a
onException ex run cleanup = catch ex run (\(e :: E.SomeException) -> cleanup >> throw ex e)

-- | 'throw' with an 'IOE' capability.
throwIO :: (E.Exception e, io :> es) => IOE io -> e -> Eff es a
throwIO io = throw (ioeToDynExn io)

-- | 'catch' with an 'IOE' capability.
catchIO :: (E.Exception e, io :> es) => IOE io -> Eff es a -> (e -> Eff es a) -> Eff es a
catchIO io = catch (ioeToDynExn io)