{-# 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 :: forall a. (forall (ex :: Effects). DynExn ex -> Eff ex a) -> a
runDynExn forall (ex :: Effects). DynExn ex -> Eff ex a
f = (forall (es :: Effects). Eff es a) -> a
forall a. (forall (es :: Effects). Eff es a) -> a
B.runPureEff (DynExn es -> Eff es a
forall (ex :: Effects). DynExn ex -> Eff ex a
f DynExn es
forall (ex :: Effects). DynExn ex
UnsafeDynExn)

-- | Refine an 'IOE' capability to a 'DynExn'.
ioeToDynExn :: IOE io -> DynExn io
ioeToDynExn :: forall (io :: Effects). IOE io -> DynExn io
ioeToDynExn IOE io
_ = DynExn io
forall (ex :: Effects). DynExn ex
UnsafeDynExn

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

-- | Catch an exception.
catch :: (E.Exception e, ex :> es) => DynExn ex -> Eff es a -> (e -> Eff es a) -> Eff es a
catch :: forall e (ex :: Effects) (es :: Effects) a.
(Exception e, ex :> es) =>
DynExn ex -> Eff es a -> (e -> Eff es a) -> Eff es a
catch DynExn ex
_ Eff es a
m e -> Eff es a
h = IO a -> Eff es a
forall (es :: Effects) a. IO a -> Eff es a
B.UnsafeMkEff (IO a -> (e -> IO a) -> IO a
forall e a. Exception e => IO a -> (e -> IO a) -> IO a
E.catch (Eff es a -> IO a
forall (es :: Effects) a. Eff es a -> IO a
B.unsafeUnEff Eff es a
m) (Eff es a -> IO a
forall (es :: Effects) a. Eff es a -> IO a
B.unsafeUnEff (Eff es a -> IO a) -> (e -> Eff es a) -> e -> IO a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. e -> Eff es a
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 :: forall (ex :: Effects) (es :: Effects) a b.
(ex :> es) =>
DynExn ex
-> Eff es a -> (a -> Eff es ()) -> (a -> Eff es b) -> Eff es b
bracket DynExn ex
ex Eff es a
acquire a -> Eff es ()
release a -> Eff es b
run = do
  a
a <- Eff es a
acquire
  DynExn ex -> Eff es b -> Eff es () -> Eff es b
forall (ex :: Effects) (es :: Effects) a.
(ex :> es) =>
DynExn ex -> Eff es a -> Eff es () -> Eff es a
finally DynExn ex
ex (a -> Eff es b
run a
a) (a -> Eff es ()
release a
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 :: forall (ex :: Effects) (es :: Effects) a.
(ex :> es) =>
DynExn ex -> Eff es a -> Eff es () -> Eff es a
finally DynExn ex
ex Eff es a
run Eff es ()
cleanup =
  DynExn ex -> Eff es a -> Eff es () -> Eff es a
forall (ex :: Effects) (es :: Effects) a.
(ex :> es) =>
DynExn ex -> Eff es a -> Eff es () -> Eff es a
onException DynExn ex
ex Eff es a
run Eff es ()
cleanup   -- if run throws an exception, then only this cleanup will run
    Eff es a -> Eff es () -> Eff es a
forall a b. Eff es a -> Eff es b -> Eff es a
forall (f :: * -> *) a b. Applicative f => f a -> f b -> f a
<* Eff es ()
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 :: forall (ex :: Effects) (es :: Effects) a.
(ex :> es) =>
DynExn ex -> Eff es a -> Eff es () -> Eff es a
onException DynExn ex
ex Eff es a
run Eff es ()
cleanup = DynExn ex -> Eff es a -> (SomeException -> Eff es a) -> Eff es a
forall e (ex :: Effects) (es :: Effects) a.
(Exception e, ex :> es) =>
DynExn ex -> Eff es a -> (e -> Eff es a) -> Eff es a
catch DynExn ex
ex Eff es a
run (\(SomeException
e :: E.SomeException) -> Eff es ()
cleanup Eff es () -> Eff es a -> Eff es a
forall a b. Eff es a -> Eff es b -> Eff es b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> DynExn ex -> SomeException -> Eff es a
forall e (ex :: Effects) (es :: Effects) a.
(Exception e, ex :> es) =>
DynExn ex -> e -> Eff es a
throw DynExn ex
ex SomeException
e)

-- | 'throw' with an 'IOE' capability.
throwIO :: (E.Exception e, io :> es) => IOE io -> e -> Eff es a
throwIO :: forall e (io :: Effects) (es :: Effects) a.
(Exception e, io :> es) =>
IOE io -> e -> Eff es a
throwIO IOE io
io = DynExn io -> e -> Eff es a
forall e (ex :: Effects) (es :: Effects) a.
(Exception e, ex :> es) =>
DynExn ex -> e -> Eff es a
throw (IOE io -> DynExn io
forall (io :: Effects). IOE io -> DynExn io
ioeToDynExn IOE io
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 :: forall e (io :: Effects) (es :: Effects) a.
(Exception e, io :> es) =>
IOE io -> Eff es a -> (e -> Eff es a) -> Eff es a
catchIO IOE io
io = DynExn io -> Eff es a -> (e -> Eff es a) -> Eff es a
forall e (ex :: Effects) (es :: Effects) a.
(Exception e, ex :> es) =>
DynExn ex -> Eff es a -> (e -> Eff es a) -> Eff es a
catch (IOE io -> DynExn io
forall (io :: Effects). IOE io -> DynExn io
ioeToDynExn IOE io
io)