From 4eedb419cf0fb98990ffb5f5458a3f9307525fe1 Mon Sep 17 00:00:00 2001
From: Jens Nolte <git@queezle.net>
Date: Tue, 5 Oct 2021 15:06:11 +0200
Subject: [PATCH] Add some documentation in the Disposable module

---
 src/Quasar/Disposable.hs | 30 +++++++++++++++++++++++-------
 1 file changed, 23 insertions(+), 7 deletions(-)

diff --git a/src/Quasar/Disposable.hs b/src/Quasar/Disposable.hs
index 3e0329e..d018b6f 100644
--- a/src/Quasar/Disposable.hs
+++ b/src/Quasar/Disposable.hs
@@ -31,16 +31,28 @@ import Quasar.Prelude
 -- * Disposable
 
 class IsDisposable a where
-  -- TODO document laws: must not throw exceptions, is idempotent
-
-  -- | Dispose a resource.
-  -- TODO MonadIO
-  dispose :: MonadIO m => a -> m (Awaitable ())
+  -- | Dispose a resource. Completion of the returned `Awaitable` signals, that the resource has been released.
+  --
+  -- Dispose should be idempotent, i.e. calling `dispose` once or multiple times should have the same effect.
+  --
+  -- `dispose` should normally be run in /masked/ state. The implementation of `dispose` has to guarantee that
+  -- resources are disposed even when encountering asynchronous exceptions, or should disable asynchronous exceptions
+  -- itself (e.g. by using `uninterruptibleMask_`).
+  --
+  -- `dispose` should also function correctly when run with uninterruptible exceptions masked.
+  dispose :: (MonadIO m, MonadMask m) => a -> m (Awaitable ())
   dispose = dispose . toDisposable
+  -- Regarding the requirements for the masking state (masked, but not uninterruptible) some arguments from
+  -- `safe-exceptions` were considered:
+  -- https://github.com/fpco/safe-exceptions/issues/3#issuecomment-230274166
 
   isDisposed :: a -> Awaitable ()
   isDisposed = isDisposed . toDisposable
 
+  -- | Convert an `IsDisposable`-Object to a `Disposable`.
+  --
+  -- When implementing the `IsDisposable`-class this can be used to defer the dispose behavior to a disposable created
+  -- by e.g. `newDisposable`.
   toDisposable :: a -> Disposable
   toDisposable = Disposable
 
@@ -140,6 +152,9 @@ newDisposable action = liftIO $ toDisposable . FnDisposable <$> newTMVarIO (Left
 synchronousDisposable :: MonadIO m => IO () -> m Disposable
 synchronousDisposable = newDisposable . fmap pure
 
+-- | A `Disposable` for which `dispose` is a no-op and which reports as already disposed.
+--
+-- Alias for `mempty`.
 noDisposable :: Disposable
 noDisposable = mempty
 
@@ -147,10 +162,11 @@ noDisposable = mempty
 newtype AlreadyDisposing = AlreadyDisposing (Awaitable ())
 
 instance IsDisposable AlreadyDisposing where
-  dispose x = pure (isDisposed x)
+  dispose = pure . isDisposed
   isDisposed (AlreadyDisposing awaitable) = awaitable
 
--- | Create a `Disposable` from an `IsAwaitable`.
+-- | Create a `Disposable` for a dispose operation which is already in progress. The awaitable passed as a parameter
+-- is used to track the completion status of the dispose operation.
 --
 -- The disposable is considered to be already disposing (so `dispose` will be a no-op) and is considered disposed once
 -- the awaitable is completed.
-- 
GitLab