diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,12 +1,25 @@
 # Changelog for `cleff`
 
+## Unreleased
+
+### Changed
+
+- **[BREAKING]** Introduces an `OnException` primitive for `Mask` that replaces `Bracket` and `BracketOnError`
+  (note that this only affects the effect datatype; there is still `bracket` and `bracketOnError` *functions* with the same semantics)
+- `runError` and `mapError` are slightly (but observably) faster now
+
+### Added
+
+- `freshEnumToState` for `Fresh`
+- `onException` and `bracketOnError_` for `Mask`
+
 ## 0.2.1.0 (2022-02-13)
 
 ### Added
 
 - Lifted convenience instances of `Bounded`, `Num`, `Fractional`, `Floating` and `IsString` for `Eff`
 - `MonadZip` instance from the `MonadComprehensions` extension for `Eff`
-- `freshEnumToState` and `runFreshAtomicCounter` for `Fresh`
+- `runFreshAtomicCounter` for `Fresh`
 - `inputToReader`, `mapInput` and `bindInput` for `Input`
 - `mapOutput` and `bindOutput` for `Output`
 - `runStateIORef`, `runStateMVar` and `runStateTVar` for `State`
diff --git a/cleff.cabal b/cleff.cabal
--- a/cleff.cabal
+++ b/cleff.cabal
@@ -5,7 +5,7 @@
 -- see: https://github.com/sol/hpack
 
 name:           cleff
-version:        0.2.1.0
+version:        0.3.0.0
 synopsis:       Fast and concise extensible effects
 description:    Please see the README on GitHub at <https://github.com/re-xyr/cleff#readme>
 category:       Control, Effect, Language
@@ -45,7 +45,6 @@
       Cleff.Input
       Cleff.Internal.Any
       Cleff.Internal.Base
-      Cleff.Internal.Effect
       Cleff.Internal.Instances
       Cleff.Internal.Interpret
       Cleff.Internal.Monad
@@ -65,6 +64,8 @@
       BlockArguments
       ConstraintKinds
       DataKinds
+      DeriveAnyClass
+      DerivingStrategies
       DerivingVia
       EmptyCase
       FlexibleContexts
@@ -77,10 +78,10 @@
       NoStarIsType
       PatternSynonyms
       PolyKinds
-      QuantifiedConstraints
       RankNTypes
       RoleAnnotations
       ScopedTypeVariables
+      StandaloneDeriving
       TemplateHaskell
       TupleSections
       TypeApplications
@@ -88,21 +89,20 @@
       TypeOperators
       UndecidableInstances
       UnicodeSyntax
-      ViewPatterns
   ghc-options: -Wall -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-deriving-strategies -Wpartial-fields -Wunused-type-patterns -Wmissing-export-lists
   build-depends:
       atomic-primops ==0.8.*
-    , base >=4.12 && <5
-    , containers ==0.6.*
+    , base >=4.12 && <4.17
+    , containers >=0.5 && <0.7
     , exceptions ==0.10.*
-    , microlens >=0.4.9 && <0.5
+    , microlens >=0.1 && <0.5
     , monad-control >=1 && <1.1
     , primitive >=0.6 && <0.8
     , rec-smallarray ==0.1.*
-    , template-haskell >=2.14 && <3
-    , th-abstraction >=0.2.11 && <0.5
-    , transformers-base >=0.4.5 && <0.5
-    , unliftio >=0.2.8 && <0.3
+    , template-haskell >=2.14 && <2.19
+    , th-abstraction >=0.2 && <0.5
+    , transformers-base >=0.1 && <0.5
+    , unliftio >=0.2.4 && <0.3
   if flag(dynamic-ioe)
     cpp-options: -DDYNAMIC_IOE
   default-language: Haskell2010
@@ -122,6 +122,8 @@
       BlockArguments
       ConstraintKinds
       DataKinds
+      DeriveAnyClass
+      DerivingStrategies
       DerivingVia
       EmptyCase
       FlexibleContexts
@@ -134,10 +136,10 @@
       NoStarIsType
       PatternSynonyms
       PolyKinds
-      QuantifiedConstraints
       RankNTypes
       RoleAnnotations
       ScopedTypeVariables
+      StandaloneDeriving
       TemplateHaskell
       TupleSections
       TypeApplications
@@ -145,24 +147,22 @@
       TypeOperators
       UndecidableInstances
       UnicodeSyntax
-      ViewPatterns
-      DeriveAnyClass
   ghc-options: -Wall -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-deriving-strategies -Wpartial-fields -Wunused-type-patterns -threaded -rtsopts -with-rtsopts=-N
   build-depends:
       atomic-primops ==0.8.*
-    , base >=4.12 && <5
+    , base >=4.12 && <4.17
     , cleff
-    , containers ==0.6.*
+    , containers >=0.5 && <0.7
     , exceptions ==0.10.*
     , extra
-    , microlens >=0.4.9 && <0.5
+    , microlens >=0.1 && <0.5
     , monad-control >=1 && <1.1
     , primitive >=0.6 && <0.8
     , rec-smallarray ==0.1.*
-    , template-haskell >=2.14 && <3
-    , th-abstraction >=0.2.11 && <0.5
-    , transformers-base >=0.4.5 && <0.5
-    , unliftio >=0.2.8 && <0.3
+    , template-haskell >=2.14 && <2.19
+    , th-abstraction >=0.2 && <0.5
+    , transformers-base >=0.1 && <0.5
+    , unliftio >=0.2.4 && <0.3
   if flag(dynamic-ioe)
     cpp-options: -DDYNAMIC_IOE
   default-language: Haskell2010
@@ -186,6 +186,8 @@
       BlockArguments
       ConstraintKinds
       DataKinds
+      DeriveAnyClass
+      DerivingStrategies
       DerivingVia
       EmptyCase
       FlexibleContexts
@@ -198,10 +200,10 @@
       NoStarIsType
       PatternSynonyms
       PolyKinds
-      QuantifiedConstraints
       RankNTypes
       RoleAnnotations
       ScopedTypeVariables
+      StandaloneDeriving
       TemplateHaskell
       TupleSections
       TypeApplications
@@ -209,28 +211,24 @@
       TypeOperators
       UndecidableInstances
       UnicodeSyntax
-      ViewPatterns
-      DeriveAnyClass
-      DeriveGeneric
   ghc-options: -Wall -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-deriving-strategies -Wpartial-fields -Wunused-type-patterns -threaded -rtsopts -with-rtsopts=-N
   build-depends:
-      QuickCheck
-    , atomic-primops ==0.8.*
-    , base >=4.12 && <5
+      atomic-primops ==0.8.*
+    , base >=4.12 && <4.17
     , cleff
-    , containers ==0.6.*
+    , containers >=0.5 && <0.7
     , exceptions ==0.10.*
     , extra
     , hspec
     , lifted-base
-    , microlens >=0.4.9 && <0.5
+    , microlens >=0.1 && <0.5
     , monad-control >=1 && <1.1
     , primitive >=0.6 && <0.8
     , rec-smallarray ==0.1.*
-    , template-haskell >=2.14 && <3
-    , th-abstraction >=0.2.11 && <0.5
-    , transformers-base >=0.4.5 && <0.5
-    , unliftio >=0.2.8 && <0.3
+    , template-haskell >=2.14 && <2.19
+    , th-abstraction >=0.2 && <0.5
+    , transformers-base >=0.1 && <0.5
+    , unliftio >=0.2.4 && <0.3
   if flag(dynamic-ioe)
     cpp-options: -DDYNAMIC_IOE
   default-language: Haskell2010
diff --git a/src/Cleff.hs b/src/Cleff.hs
--- a/src/Cleff.hs
+++ b/src/Cleff.hs
@@ -9,7 +9,7 @@
 -- This library implements an /extensible effects system/, where sets of monadic actions ("effects") are encoded as
 -- datatypes, tracked at the type level and can have multiple different implementations. This means you can swap out
 -- implementations of certain monadic actions in mock tests or in different environments. The notion of "effect" is
--- general here: it can be an 'IO'-performing side effect, or just obtaining the value of a static global environment.
+-- general here: it can be an 'IO'-performing side effect, or just reading the value of a static global environment.
 --
 -- In particular, this library consists of
 --
@@ -20,43 +20,77 @@
 -- * Combinators for defining new effects and interpreting them /on your own/. These effects can be translated in terms
 --   of other already existing effects, or into operations in the 'IO' monad.
 --
--- So, this library allows you to do two things:
+-- In terms of structuring your application, this library helps you to do two things:
 --
 -- * __Effect management:__ The 'Eff' monad tracks what effects are used explicitly at the type level, therefore you
---   are able to be certain about what effects are involved in each function.
--- * __Effect decoupling:__ You can decouple the implementation of the effects from your application and swap them
---   easily.
+--   are able to enforce what effects are involved in each function, and avoid accidentally introduced behaviors.
+-- * __Effect decoupling:__ You can swap between the implementations of the effects in your application easily,
+--   so you can refactor and test your applications with less clutter.
 module Cleff
   ( -- * Using effects
-    Eff, (:>), (:>>), Effect, IOE
-  , -- ** Running effects
+    Eff
+  , (:>)
+  , (:>>)
+  , Effect
+  , IOE
+    -- ** Running effects
     -- $runningEffects
-    runPure, runIOE
-  , -- * Defining effects
+  , runPure
+  , runIOE
+    -- * Defining effects
     -- $definingEffects
-    send, sendVia, makeEffect, makeEffect_
-  , -- * Trivial effects handling
-    raise, raiseN, inject, subsume, subsumeN, KnownList, Subset
-  , -- * Interpreting effects
+  , send
+  , sendVia
+  , makeEffect
+  , makeEffect_
+    -- * Trivial effects handling
+  , raise
+  , raiseN
+  , inject
+  , subsume
+  , subsumeN
+  , KnownList
+  , Subset
+    -- * Interpreting effects
     -- $interpretingEffects
-    Handler, interpret, reinterpret, reinterpret2, reinterpret3, reinterpretN, interpose, impose, imposeN
-  , -- ** Interpreting in terms of 'IO'
-    HandlerIO, interpretIO
-  , -- ** Translating effects
-    Translator, transform, translate
-  , -- ** Transforming interpreters
-    raiseUnder, raiseNUnder, raiseUnderN, raiseNUnderN
-  , -- * Combinators for interpreting higher order effects
+  , Handler
+  , interpret
+  , reinterpret
+  , reinterpret2
+  , reinterpret3
+  , reinterpretN
+  , interpose
+  , impose
+  , imposeN
+    -- ** Interpreting in terms of 'IO'
+  , HandlerIO
+  , interpretIO
+    -- ** Translating effects
+  , Translator
+  , transform
+  , translate
+    -- ** Transforming interpreters
+  , raiseUnder
+  , raiseNUnder
+  , raiseUnderN
+  , raiseNUnderN
+    -- * Combinators for interpreting higher order effects
     -- $higherOrderEffects
-    Handling, toEff, toEffWith, withFromEff
-  , -- ** Interpreting 'IO'-related higher order effects
-    withToIO, fromIO
-  , -- * Miscellaneous
-    type (~>), type (++), MonadIO (..), MonadUnliftIO (..)
+  , Handling
+  , toEff
+  , toEffWith
+  , withFromEff
+    -- ** Interpreting 'IO'-related higher order effects
+  , withToIO
+  , fromIO
+    -- * Miscellaneous
+  , type (~>)
+  , type (++)
+  , MonadIO (..)
+  , MonadUnliftIO (..)
   ) where
 
 import           Cleff.Internal.Base
-import           Cleff.Internal.Effect
 import           Cleff.Internal.Instances ()
 import           Cleff.Internal.Interpret
 import           Cleff.Internal.Monad
@@ -64,21 +98,21 @@
 import           UnliftIO                 (MonadIO (liftIO), MonadUnliftIO (withRunInIO))
 
 -- $runningEffects
--- To run an effect @T@, we should use an /interpreter/ of @T@, which is a function that has type like this:
+-- To run an effect @T@, we should use an /interpreter/ of @T@, which is a function that has a type like this:
 --
 -- @
 -- runT :: 'Eff' (T ': es) a -> 'Eff' es a
 -- @
 --
 -- Such an interpreter provides an implementation of @T@ and eliminates @T@ from the effect stack. All builtin effects
--- in @cleff@ have interpreters coming together with them.
+-- in @cleff@ have interpreters out of the box in their respective modules.
 --
 -- By applying interpreters to an 'Eff' computation, you can eventually obtain an /end computation/, where there are no
--- more effects present on the effect stack. There are two kinds of end computations:
+-- more effects to be interpreted on the effect stack. There are two kinds of end computations:
 --
--- * A /pure computation/ with the type @'Eff' '[] a@, which you can obtain the value via 'Cleff.runPure'; or,
--- * An /impure computation/ with type @'Eff' '['Cleff.IOE'] a@ that can be transformed into an IO computation via
---   'Cleff.runIOE'.
+-- * A /pure computation/ with the type @'Eff' '[] a@, which you can obtain the value via 'runPure'; or,
+-- * An /impure computation/ with type @'Eff' '['IOE'] a@ that can be unwrapped into an IO computation via
+--   'runIOE'.
 
 -- $definingEffects
 -- An effect should be defined as a GADT and have the kind 'Effect'. Each operation in the effect is a constructor of
diff --git a/src/Cleff/Error.hs b/src/Cleff/Error.hs
--- a/src/Cleff/Error.hs
+++ b/src/Cleff/Error.hs
@@ -9,51 +9,78 @@
 module Cleff.Error
   ( -- * Effect
     Error (..)
-  , -- * Operations
-    throwError, catchError, fromEither, fromException, fromExceptionVia, fromExceptionEff, fromExceptionEffVia,
-    note, catchErrorJust, catchErrorIf, handleError, handleErrorJust, handleErrorIf, tryError, tryErrorJust
-  , -- * Interpretations
-    runError, mapError
+    -- * Operations
+  , throwError
+  , catchError
+    -- ** Other ways of throwing errors
+  , fromEither
+  , fromException
+  , fromExceptionVia
+  , fromExceptionEff
+  , fromExceptionEffVia
+  , note
+    -- ** Other ways of handling errors
+  , catchErrorJust
+  , catchErrorIf
+  , handleError
+  , handleErrorJust
+  , handleErrorIf
+  , tryError
+  , tryErrorJust
+    -- * Interpretations
+  , runError
+  , mapError
   ) where
 
 import           Cleff
 import           Cleff.Internal.Any
 import           Cleff.Internal.Base
-import           Control.Exception   (Exception)
-import           Data.Bool           (bool)
-import           Data.Unique         (Unique, hashUnique, newUnique)
-import qualified UnliftIO.Exception  as Exc
+import           Control.Exception    (Exception)
+import           Data.Atomics.Counter (AtomicCounter, incrCounter, newCounter)
+import           Data.Bool            (bool)
+import           System.IO.Unsafe     (unsafePerformIO)
+import qualified UnliftIO.Exception   as Exc
 
 -- * Effect
 
--- | An effect capable of breaking out of current control flow by raising an exceptional value @e@. This effect roughly
--- corresponds to the @MonadError@ typeclass and @ExceptT@ monad transformer in @mtl@.
+-- | An effect capable of breaking out of current control flow by throwing an error of type @e@, and handling the
+-- errors thrown from computations. This effect roughly corresponds to the @MonadError@ typeclass and @ExceptT@ monad
+-- transformer in @mtl@.
 data Error e :: Effect where
   ThrowError :: e -> Error e m a
   CatchError :: m a -> (e -> m a) -> Error e m a
 
 -- * Operations
 
-makeEffect ''Error
+makeEffect_ ''Error
 
+-- | Throw an error in the current computation.
+throwError :: Error e :> es => e -> Eff es a
+
+-- | Handle an error if one is thrown from a computation, and then return to normal control flow.
+catchError :: Error e :> es
+  => Eff es a -- ^ The computation that may throw errors
+  -> (e -> Eff es a) -- ^ The handler that is called when an error is thrown
+  -> Eff es a
+
 -- | Lift an 'Either' value into the 'Error' effect.
 fromEither :: Error e :> es => Either e a -> Eff es a
 fromEither = either throwError pure
 
 -- | Lift exceptions generated by an 'IO' computation into the 'Error' effect.
-fromException :: ∀ e es a. (Exc.Exception e, '[Error e, IOE] :>> es) => IO a -> Eff es a
+fromException :: ∀ e es a. (Exception e, '[Error e, IOE] :>> es) => IO a -> Eff es a
 fromException m = Exc.catch (liftIO m) (throwError @e)
 
 -- | Like 'fromException', but allows to transform the exception into another error type.
-fromExceptionVia :: (Exc.Exception ex, '[Error er, IOE] :>> es) => (ex -> er) -> IO a -> Eff es a
+fromExceptionVia :: (Exception ex, '[Error er, IOE] :>> es) => (ex -> er) -> IO a -> Eff es a
 fromExceptionVia f m = Exc.catch (liftIO m) (throwError . f)
 
 -- | Lift exceptions generated by an 'Eff' computation into the 'Error' effect.
-fromExceptionEff :: ∀ e es a. (Exc.Exception e, '[Error e, IOE] :>> es) => Eff es a -> Eff es a
+fromExceptionEff :: ∀ e es a. (Exception e, '[Error e, IOE] :>> es) => Eff es a -> Eff es a
 fromExceptionEff m = withRunInIO \unlift -> Exc.catch (unlift m) (unlift . throwError @e)
 
 -- | Like 'fromExceptionEff', but allows to transform the exception into another error type.
-fromExceptionEffVia :: (Exc.Exception ex, '[Error er, IOE] :>> es) => (ex -> er) -> Eff es a -> Eff es a
+fromExceptionEffVia :: (Exception ex, '[Error er, IOE] :>> es) => (ex -> er) -> Eff es a -> Eff es a
 fromExceptionEffVia f m = withRunInIO \unlift -> Exc.catch (unlift m) (unlift . throwError . f)
 
 -- | Try to extract a value from 'Maybe', throw an error otherwise.
@@ -93,27 +120,37 @@
 
 -- * Interpretations
 
--- | Exception wrapper used in 'runError' in order not to conflate error types with exception types.
-data ErrorExc = ErrorExc !Unique Any
+type ExcUid = Int
 
-instance Exception ErrorExc
+-- | Exception wrapper used in 'runError' in order not to conflate error types with exception types.
+data ErrorExc = ErrorExc {-# UNPACK #-} !ExcUid Any
+  deriving anyclass (Exception)
 
 instance Show ErrorExc where
   showsPrec _ (ErrorExc uid _) =
-    ("Cleff.Error.runError: Escaped error (error UID hash: " <>) . shows (hashUnique uid) . ("). This is possibly due \
+    ("Cleff.Error.runError: Escaped error (error UID: " <>) . shows uid . ("). This is possibly due \
     \to trying to 'throwError' in a forked thread, or trying to 'wait' on an error-throwing 'Async' computation out \
     \of the effect scope where it is created. Refer to the haddock of 'runError' for details on the caveats. If all \
     \those shenanigans mentioned or other similar ones seem unlikely, please report this as a bug." <>)
 
-catch' :: ∀ e m a. MonadUnliftIO m => Unique -> m a -> (e -> m a) -> m a
-catch' eid m h = m `Exc.catch` \ex@(ErrorExc eid' e) -> if eid == eid' then h (fromAny e) else Exc.throwIO ex
+catch' :: ∀ e m a. MonadUnliftIO m => ExcUid -> m a -> (e -> m a) -> m a
+catch' eid m h = m `Exc.catch` \ex@(ErrorExc eid' e) ->
+  if eid == eid' then h (fromAny e) else Exc.throwIO ex
 {-# INLINE catch' #-}
 
-try' :: ∀ e m a. MonadUnliftIO m => Unique -> m a -> m (Either e a)
+try' :: ∀ e m a. MonadUnliftIO m => ExcUid -> m a -> m (Either e a)
 try' eid m = catch' eid (Right <$> m) (pure . Left)
 {-# INLINE try' #-}
 
-errorHandler :: Unique -> Handler (Error e) (IOE ': es)
+excUidSource :: AtomicCounter
+excUidSource = unsafePerformIO (newCounter 0)
+{-# NOINLINE excUidSource #-}
+
+newExcUid :: IO ExcUid
+newExcUid = incrCounter 1 excUidSource
+{-# INLINE newExcUid #-}
+
+errorHandler :: ExcUid -> Handler (Error e) (IOE ': es)
 errorHandler eid = \case
   ThrowError e     -> Exc.throwIO $ ErrorExc eid (toAny e)
   CatchError m' h' -> withToIO \toIO -> liftIO $ catch' eid (toIO m') (toIO . h')
@@ -121,7 +158,9 @@
 
 -- | Run an 'Error' effect.
 --
--- __Caveat__: 'runError' is implemented with 'Exc.Exception's therefore inherits some of its unexpected behaviors.
+-- === Caveats
+--
+-- 'runError' is implemented with 'Exc.Exception's therefore inherits some of its unexpected behaviors.
 -- Errors thrown in forked threads will /not/ be directly caught by 'catchError's in the parent thread. Instead it will
 -- incur an exception, and we won't be quite able to display the details of that exception properly at that point.
 -- Therefore please properly handle the errors in the forked threads separately.
@@ -132,7 +171,7 @@
 -- will again not be caught. The best choice is /not to pass @Async@ values around randomly/.
 runError :: ∀ e es a. Eff (Error e ': es) a -> Eff es (Either e a)
 runError m = thisIsPureTrustMe do
-  eid <- liftIO newUnique
+  eid <- liftIO newExcUid
   try' eid $ reinterpret (errorHandler eid) m
 {-# INLINE runError #-}
 
@@ -141,7 +180,7 @@
 mapError f = thisIsPureTrustMe . reinterpret \case
   ThrowError e   -> throwError $ f e
   CatchError m h -> do
-    eid <- liftIO newUnique
+    eid <- liftIO newExcUid
     res <- try' @e eid $ toEffWith (errorHandler eid) m
     case res of
       Left e  -> toEff (h e)
diff --git a/src/Cleff/Fail.hs b/src/Cleff/Fail.hs
--- a/src/Cleff/Fail.hs
+++ b/src/Cleff/Fail.hs
@@ -9,8 +9,9 @@
 module Cleff.Fail
   ( -- * Effect
     Fail (..)
-  , -- * Interpretations
-    runFail, runFailIO
+    -- * Interpretations
+  , runFail
+  , runFailIO
   ) where
 
 import           Cleff
diff --git a/src/Cleff/Fresh.hs b/src/Cleff/Fresh.hs
--- a/src/Cleff/Fresh.hs
+++ b/src/Cleff/Fresh.hs
@@ -8,10 +8,13 @@
 module Cleff.Fresh
   ( -- * Effect
     Fresh (..)
-  , -- * Operations
-    fresh
-  , -- * Interpretations
-    freshIntToState, runFreshAtomicCounter, runFreshUnique
+    -- * Operations
+  , fresh
+    -- * Interpretations
+  , freshIntToState
+  , freshEnumToState
+  , runFreshAtomicCounter
+  , runFreshUnique
   ) where
 
 import           Cleff
@@ -28,7 +31,10 @@
 
 -- * Operations
 
-makeEffect ''Fresh
+makeEffect_ ''Fresh
+
+-- | Obtain a fresh unique value.
+fresh :: Fresh u :> es => Eff es u
 
 -- * Interpretations
 
diff --git a/src/Cleff/Input.hs b/src/Cleff/Input.hs
--- a/src/Cleff/Input.hs
+++ b/src/Cleff/Input.hs
@@ -8,10 +8,16 @@
 module Cleff.Input
   ( -- * Effect
     Input (..)
-  , -- * Operations
-    input, inputs
-  , -- * Interpretations
-    runInputConst, inputToListState, inputToReader, runInputEff, mapInput, bindInput
+    -- * Operations
+  , input
+  , inputs
+    -- * Interpretations
+  , runInputConst
+  , inputToListState
+  , inputToReader
+  , runInputEff
+  , mapInput
+  , bindInput
   ) where
 
 import           Cleff
@@ -26,7 +32,10 @@
 
 -- * Operations
 
-makeEffect ''Input
+makeEffect_ ''Input
+
+-- | Read an input value from an input source.
+input :: Input i :> es => Eff es i
 
 -- | Apply a function to the result of 'input'.
 inputs :: Input i :> es => (i -> i') -> Eff es i'
diff --git a/src/Cleff/Internal/Base.hs b/src/Cleff/Internal/Base.hs
--- a/src/Cleff/Internal/Base.hs
+++ b/src/Cleff/Internal/Base.hs
@@ -17,17 +17,21 @@
 module Cleff.Internal.Base
   ( -- * The 'IOE' Effect
     IOE
-  , -- * Primitive 'IO' functions
-    primLiftIO, primUnliftIO
-  , -- * Unwrapping 'Eff'
-    thisIsPureTrustMe, runIOE, runPure
-  , -- * Effect interpretation
-    HandlerIO, interpretIO
-  , -- * Combinators for interpreting higher-order effects
-    withToIO, fromIO
+    -- * Primitive 'IO' functions
+  , primLiftIO
+  , primUnliftIO
+    -- * Unwrapping 'Eff'
+  , thisIsPureTrustMe
+  , runIOE
+  , runPure
+    -- * Effect interpretation
+  , HandlerIO
+  , interpretIO
+    -- * Combinators for interpreting higher-order effects
+  , withToIO
+  , fromIO
   ) where
 
-import           Cleff.Internal.Effect
 import           Cleff.Internal.Interpret
 import           Cleff.Internal.Monad
 import           Control.Monad.Base          (MonadBase (liftBase))
@@ -43,22 +47,24 @@
 
 -- * The 'IOE' effect
 
--- | The effect for lifting and unlifting the 'IO' monad, allowing you to use 'MonadIO', 'MonadUnliftIO', 'PrimMonad',
--- 'MonadCatch', 'MonadThrow' and 'MonadMask' functionalities. This is the "final" effect that most effects eventually
--- are interpreted into. For example, you can do:
+-- | The effect capable of lifting and unlifting the 'IO' monad, allowing you to use 'MonadIO', 'MonadUnliftIO',
+-- 'PrimMonad', 'MonadCatch', 'MonadThrow' and 'MonadMask' functionalities. This is the "final" effect that most
+-- effects eventually are interpreted into. For example, you can do:
 --
 -- @
 -- log :: 'IOE' :> es => 'Eff' es ()
 -- log = 'liftIO' ('putStrLn' "Test logging")
 -- @
 --
--- It is not recommended to use this effect in application code, as it is too liberal and allows arbitrary IO. Ideally,
--- this is only used in interpreting more fine-grained effects.
+-- It is not recommended to use this effect directly in application code, as it is too liberal and allows arbitrary IO,
+-- therefore making it harder to do proper effect management. Ideally, this is only used in interpreting more
+-- fine-grained effects.
 --
+-- === Technical details
+--
 -- Note that this is /not/ a real effect and cannot be interpreted in any way besides 'thisIsPureTrustMe' and
--- 'runIOE'. It is similar to Polysemy's @Final@ effect which also cannot be interpreted. This is mainly for
--- performance concern, but also that there doesn't really exist reasonable interpretations other than the current one,
--- given the underlying implementation of the 'Eff' monad.
+-- 'runIOE'. This is mainly for performance concern, but also that there doesn't really exist reasonable
+-- interpretations other than the current one, given the underlying implementation of the 'Eff' monad.
 --
 -- 'IOE' can be a real effect though, and you can enable the @dynamic-ioe@ build flag to have that. However it is only
 -- for reference purposes and should not be used in production code.
diff --git a/src/Cleff/Internal/Effect.hs b/src/Cleff/Internal/Effect.hs
deleted file mode 100644
--- a/src/Cleff/Internal/Effect.hs
+++ /dev/null
@@ -1,33 +0,0 @@
-{-# OPTIONS_HADDOCK not-home #-}
--- |
--- Copyright: (c) 2021 Xy Ren
--- License: BSD3
--- Maintainer: xy.r@outlook.com
--- Stability: unstable
--- Portability: non-portable (GHC only)
---
--- This module contains definitions of some basic types related to effects. You won't need this module directly;
--- these functionalities are reexported in the "Cleff" module.
---
--- __This is an /internal/ module and its API may change even between minor versions.__ Therefore you should be
--- extra careful if you're to depend on this module.
-module Cleff.Internal.Effect (Effect, (:>), (:>>), type (++), type (~>)) where
-
-import           Data.Kind           (Constraint, Type)
-import           Data.Rec.SmallArray (Elem, type (++), type (~>))
-
--- | The type of effects. An effect @e m a@ takes an effect monad type @m :: 'Type' -> 'Type'@ and a result type
--- @a :: 'Type'@.
-type Effect = (Type -> Type) -> Type -> Type
-
--- | @e ':>' es@ means the effect @e@ is present in the effect stack @es@, and therefore can be used in an
--- @'Cleff.Eff' es@ computation.
-type (:>) = Elem
-infix 0 :>
-
--- | @xs ':>>' es@ means the list of effects @xs@ are all present in the effect stack @es@. This is a convenient type
--- alias for @(e1 ':>' es, ..., en ':>' es)@.
-type family xs :>> es :: Constraint where
-  '[] :>> _ = ()
-  (x ': xs) :>> es = (x :> es, xs :>> es)
-infix 0 :>>
diff --git a/src/Cleff/Internal/Instances.hs b/src/Cleff/Internal/Instances.hs
--- a/src/Cleff/Internal/Instances.hs
+++ b/src/Cleff/Internal/Instances.hs
@@ -13,15 +13,14 @@
 -- extra careful if you're to depend on this module.
 module Cleff.Internal.Instances () where
 
-import           Cleff.Internal.Monad (Eff)
+import           Cleff.Internal.Monad (Eff (Eff))
 import           Control.Applicative  (Applicative (liftA2))
 import           Control.Monad.Zip    (MonadZip (munzip, mzipWith))
+import           Data.Monoid          (Ap (Ap))
 import           Data.String          (IsString (fromString))
 
 -- | @since 0.2.1.0
-instance Bounded a => Bounded (Eff es a) where
-  minBound = pure minBound
-  maxBound = pure maxBound
+deriving via (Ap (Eff es) a) instance Bounded a => Bounded (Eff es a)
 
 -- | @since 0.2.1.0
 instance Num a => Num (Eff es a) where
@@ -61,12 +60,10 @@
   atanh = fmap atanh
 
 -- | @since 0.2.1.0
-instance Semigroup a => Semigroup (Eff es a) where
-  (<>) = liftA2 (<>)
+deriving newtype instance Semigroup a => Semigroup (Eff es a)
 
 -- | @since 0.2.1.0
-instance Monoid a => Monoid (Eff es a) where
-  mempty = pure mempty
+deriving newtype instance Monoid a => Monoid (Eff es a)
 
 -- | @since 0.2.1.0
 instance IsString a => IsString (Eff es a) where
diff --git a/src/Cleff/Internal/Interpret.hs b/src/Cleff/Internal/Interpret.hs
--- a/src/Cleff/Internal/Interpret.hs
+++ b/src/Cleff/Internal/Interpret.hs
@@ -15,22 +15,43 @@
 -- extra careful if you're to depend on this module.
 module Cleff.Internal.Interpret
   ( -- * Trivial handling
-    adjust, raise, raiseN, inject, subsume, subsumeN, raiseUnder, raiseNUnder, raiseUnderN, raiseNUnderN
-  , -- * Handler types
-    Handling, esSend, Handler, Translator
-  , -- * Interpreting effects
-    interpret, reinterpret, reinterpret2, reinterpret3, reinterpretN, interpose, impose, imposeN
-  , -- * Translating effects
-    transform, translate
-  , -- * Combinators for interpreting higher effects
-    toEff, toEffWith, withFromEff
+    adjust
+  , raise
+  , raiseN
+  , inject
+  , subsume
+  , subsumeN
+  , raiseUnder
+  , raiseNUnder
+  , raiseUnderN
+  , raiseNUnderN
+    -- * Handler types
+  , Handling
+  , esSend
+  , Handler
+  , Translator
+    -- * Interpreting effects
+  , interpret
+  , reinterpret
+  , reinterpret2
+  , reinterpret3
+  , reinterpretN
+  , interpose
+  , impose
+  , imposeN
+    -- * Translating effects
+  , transform
+  , translate
+    -- * Combinators for interpreting higher effects
+  , toEff
+  , toEffWith
+  , withFromEff
   ) where
 
-import           Cleff.Internal.Effect
 import           Cleff.Internal.Monad
-import           Data.Rec.SmallArray   (Rec, pattern (:++:))
-import qualified Data.Rec.SmallArray   as Rec
-import           Unsafe.Coerce         (unsafeCoerce)
+import           Data.Rec.SmallArray  (Rec, pattern (:++:))
+import qualified Data.Rec.SmallArray  as Rec
+import           Unsafe.Coerce        (unsafeCoerce)
 
 -- * Trivial handling
 
diff --git a/src/Cleff/Internal/Monad.hs b/src/Cleff/Internal/Monad.hs
--- a/src/Cleff/Internal/Monad.hs
+++ b/src/Cleff/Internal/Monad.hs
@@ -13,23 +13,60 @@
 -- __This is an /internal/ module and its API may change even between minor versions.__ Therefore you should be
 -- extra careful if you're to depend on this module.
 module Cleff.Internal.Monad
-  ( -- * The 'Eff' monad
-    InternalHandler (InternalHandler, runHandler), Eff (Eff, unEff)
-  , -- * Effect environment
-    Env, HandlerPtr, emptyEnv, adjustEnv, allocaEnv, readEnv, writeEnv, replaceEnv, appendEnv, updateEnv
-  , -- * Performing effect operations
-    KnownList, Subset, send, sendVia
+  ( -- * Basic types
+    Effect
+  , type (:>)
+  , type (:>>)
+  , type (~>)
+  , type (++)
+    -- * The 'Eff' monad
+  , InternalHandler (InternalHandler, runHandler)
+  , Eff (Eff, unEff)
+    -- * Effect environment
+  , Env
+  , HandlerPtr
+  , emptyEnv
+  , adjustEnv
+  , allocaEnv
+  , readEnv
+  , writeEnv
+  , replaceEnv
+  , appendEnv
+  , updateEnv
+    -- * Performing effect operations
+  , KnownList
+  , Subset
+  , send
+  , sendVia
   ) where
 
 import           Cleff.Internal.Any
-import           Cleff.Internal.Effect
-import           Control.Applicative   (Applicative (liftA2))
-import           Control.Monad.Fix     (MonadFix (mfix))
-import           Data.IntMap.Strict    (IntMap)
-import qualified Data.IntMap.Strict    as Map
-import           Data.Rec.SmallArray   (KnownList, Rec, Subset, pattern (:~:))
-import qualified Data.Rec.SmallArray   as Rec
+import           Control.Applicative (Applicative (liftA2))
+import           Control.Monad.Fix   (MonadFix (mfix))
+import           Data.IntMap.Strict  (IntMap)
+import qualified Data.IntMap.Strict  as Map
+import           Data.Kind           (Constraint, Type)
+import           Data.Rec.SmallArray (Elem, KnownList, Rec, Subset, pattern (:~:), type (++), type (~>))
+import qualified Data.Rec.SmallArray as Rec
 
+-- * Basic types
+
+-- | The type of effects. An effect @e m a@ takes an effect monad type @m :: 'Type' -> 'Type'@ and a result type
+-- @a :: 'Type'@.
+type Effect = (Type -> Type) -> Type -> Type
+
+-- | @e ':>' es@ means the effect @e@ is present in the effect stack @es@, and therefore can be used in an
+-- @'Cleff.Eff' es@ computation.
+type (:>) = Elem
+infix 0 :>
+
+-- | @xs ':>>' es@ means the list of effects @xs@ are all present in the effect stack @es@. This is a convenient type
+-- alias for @(e1 ':>' es, ..., en ':>' es)@.
+type family xs :>> es :: Constraint where
+  '[] :>> _ = ()
+  (x ': xs) :>> es = (x :> es, xs :>> es)
+infix 0 :>>
+
 -- * The 'Eff' monad
 
 -- | The internal representation of effect handlers. This is just a natural transformation from the effect type
@@ -37,19 +74,18 @@
 --
 -- In interpreting functions (see "Cleff.Internal.Interpret"), the user-facing 'Cleff.Handler' type is transformed into
 -- this type.
-newtype InternalHandler e = InternalHandler
-  { runHandler :: ∀ es. e (Eff es) ~> Eff es }
+newtype InternalHandler e = InternalHandler { runHandler :: ∀ es. e (Eff es) ~> Eff es }
 
--- | The extensible effect monad. A monad @'Eff' es@ is capable of performing any effect in the /effect stack/ @es@,
+-- | The extensible effects monad. The monad @'Eff' es@ is capable of performing any effect in the /effect stack/ @es@,
 -- which is a type-level list that holds all effects available. However, most of the times, for flexibility, @es@
 -- should be a polymorphic type variable, and you should use the '(:>)' and '(:>>)' operators in constraints to
 -- indicate what effects are in the stack. For example,
 --
 -- @
--- 'Cleff.Reader.Reader' 'String' ':>' es, 'Cleff.State.State' 'Bool' ':>' es => 'Eff' es 'Integer'
+-- ('Cleff.Reader.Reader' 'String' ':>' es, 'Cleff.State.State' 'Bool' ':>' es) => 'Eff' es 'Integer'
 -- @
 --
--- allows you to perform operations of the @'Cleff.Reader.Reader' 'String'@ effect and the @'Cleff.State.State' 'Bool'@
+-- means you can perform operations of the @'Cleff.Reader.Reader' 'String'@ effect and the @'Cleff.State.State' 'Bool'@
 -- effect in a computation returning an 'Integer'.
 type role Eff nominal representational
 newtype Eff es a = Eff { unEff :: Env es -> IO a }
@@ -75,9 +111,10 @@
   {-# INLINE liftA2 #-}
 
 instance Monad (Eff es) where
+  -- no 'return', because the default impl is correct and it is going to be deprecated anyway
   Eff x >>= f = Eff \es -> x es >>= \x' -> unEff (f x') es
   {-# INLINE (>>=) #-}
-  (>>) = (*>)
+  (>>) = (*>) -- More efficient, since the default is @x >> y = x >>= const y@
   {-# INLINE (>>) #-}
 
 instance MonadFix (Eff es) where
@@ -92,10 +129,7 @@
 -- effect interpretation ('Cleff.reinterpretN') and the latter for local interpretation ('Cleff.toEffWith') in order to
 -- retain correct HO semantics. For more details on this see https://github.com/re-xyr/cleff/issues/5.
 type role Env nominal
-data Env (es :: [Effect]) = Env
-  {-# UNPACK #-} !(Rec HandlerPtr es) -- ^ The array.
-  {-# UNPACK #-} !Int -- ^ The next memory address to allocate.
-  !(IntMap Any) -- ^ The simulated memory.
+data Env (es :: [Effect]) = Env {-# UNPACK #-} !Int {-# UNPACK #-} !(Rec HandlerPtr es) !(IntMap Any)
 
 -- | A pointer to 'InternalHandler' in an 'Env'.
 type role HandlerPtr nominal
@@ -103,42 +137,42 @@
 
 -- | Create an empty 'Env' with no address allocated.
 emptyEnv :: Env '[]
-emptyEnv = Env Rec.empty 0 Map.empty
+emptyEnv = Env 0 Rec.empty Map.empty
 {-# INLINE emptyEnv #-}
 
 -- | Adjust the effect stack via an function over 'Rec'.
 adjustEnv :: ∀ es' es. (Rec HandlerPtr es -> Rec HandlerPtr es') -> Env es -> Env es'
-adjustEnv f (Env re n mem) = Env (f re) n mem
+adjustEnv f (Env n re mem) = Env n (f re) mem
 {-# INLINE adjustEnv #-}
 
 -- | Allocate a new, empty address for a handler. \( O(1) \).
 allocaEnv :: ∀ e es. Env es -> (# HandlerPtr e, Env es #)
-allocaEnv (Env re n mem) = (# HandlerPtr n, Env re (succ n) mem #)
+allocaEnv (Env n re mem) = (# HandlerPtr n, Env (n + 1) re mem #)
 {-# INLINE allocaEnv #-}
 
 -- | Read the handler a pointer points to. \( O(1) \).
 readEnv :: ∀ e es. Rec.Elem e es => Env es -> InternalHandler e
-readEnv (Env re _ mem) = fromAny $ mem Map.! unHandlerPtr (Rec.index @e re)
+readEnv (Env _ re mem) = fromAny $ mem Map.! unHandlerPtr (Rec.index @e re)
 {-# INLINE readEnv #-}
 
 -- | Overwrite the handler a pointer points to. \( O(1) \).
 writeEnv :: ∀ e es. HandlerPtr e -> InternalHandler e -> Env es -> Env es
-writeEnv (HandlerPtr m) x (Env re n mem) = Env re n (Map.insert m (toAny x) mem)
+writeEnv (HandlerPtr m) x (Env n re mem) = Env n re (Map.insert m (toAny x) mem)
 {-# INLINE writeEnv #-}
 
 -- | Replace the handler pointer of an effect in the stack. \( O(n) \).
 replaceEnv :: ∀ e es. Rec.Elem e es => HandlerPtr e -> InternalHandler e -> Env es -> Env es
-replaceEnv (HandlerPtr m) x (Env re n mem) = Env (Rec.update @e (HandlerPtr m) re) n (Map.insert m (toAny x) mem)
+replaceEnv (HandlerPtr m) x (Env n re mem) = Env n (Rec.update @e (HandlerPtr m) re) (Map.insert m (toAny x) mem)
 {-# INLINE replaceEnv #-}
 
 -- | Add a new effect to the stack with its corresponding handler pointer. \( O(n) \).
 appendEnv :: ∀ e es. HandlerPtr e -> InternalHandler e -> Env es -> Env (e ': es)
-appendEnv (HandlerPtr m) x (Env re n mem) = Env (HandlerPtr m :~: re) n (Map.insert m (toAny x) mem)
+appendEnv (HandlerPtr m) x (Env n re mem) = Env n (HandlerPtr m :~: re) (Map.insert m (toAny x) mem)
 {-# INLINE appendEnv #-}
 
 -- | Use the state of LHS as a newer version for RHS. \( O(1) \).
 updateEnv :: ∀ es es'. Env es' -> Env es -> Env es
-updateEnv (Env _ n mem) (Env re' _ _) = Env re' n mem
+updateEnv (Env n _ mem) (Env _ re' _) = Env n re' mem
 {-# INLINE updateEnv #-}
 
 -- * Performing effect operations
@@ -149,8 +183,8 @@
 send = sendVia id
 
 -- | Perform an action in another effect stack via a transformation to that stack; in other words, this function "maps"
--- the effect operation from effect stack @es@ to @es'@. This is a generalization of 'send'; end users most likely
--- won't need to use this.
+-- the effect operation from effect stack @es@ to @es'@. This is a largely generalized version of 'send'; only use this
+-- if you are sure about what you're doing.
 --
 -- @
 -- 'send' = 'sendVia' 'id'
diff --git a/src/Cleff/Internal/TH.hs b/src/Cleff/Internal/TH.hs
--- a/src/Cleff/Internal/TH.hs
+++ b/src/Cleff/Internal/TH.hs
@@ -15,7 +15,6 @@
 -- extra careful if you're to depend on this module.
 module Cleff.Internal.TH (makeEffect, makeEffect_) where
 
-import           Cleff.Internal.Effect
 import           Cleff.Internal.Monad
 import           Control.Monad                (join)
 import           Data.Char                    (toLower)
diff --git a/src/Cleff/Mask.hs b/src/Cleff/Mask.hs
--- a/src/Cleff/Mask.hs
+++ b/src/Cleff/Mask.hs
@@ -8,30 +8,97 @@
 module Cleff.Mask
   ( -- * Effect
     Mask (..)
-  , -- * Operations
-    mask, uninterruptibleMask, bracket, bracketOnError, mask_, uninterruptibleMask_, bracket_, finally, onError
-  , -- * Interpretations
-    runMask
+    -- * High-level operations
+  , bracket
+  , bracketOnError
+  , bracket_
+  , bracketOnError_
+  , onError
+  , finally
+    -- * Primitive operations
+  , mask
+  , uninterruptibleMask
+  , onException
+  , mask_
+  , uninterruptibleMask_
+    -- * Interpretations
+  , runMask
   ) where
 
 import           Cleff
 import           Cleff.Internal.Base
-import qualified UnliftIO.Exception  as Exc
+import qualified Control.Exception   as Exc
 
 -- * Effect
 
--- | An effect capable of 'Exc.mask'ing and specifically, 'Exc.bracket'ing operations, /i.e./ allowing cleanup after
--- operations that my raise exceptions.
+-- | An effect capable of 'Exc.mask'ing and performing cleanup operations when an computation is interrupted. In
+-- particular, this effects allows the use of 'bracket'.
+--
+-- === Technical details
+--
+-- Regarding the nuances of 'bracket' semantics, this effect uses the semantics of "UnliftIO.Exception" rather than
+-- "Control.Exception". They are more sensible defaults and users can implement other semantics out of the primitive
+-- operations if they want to.
 data Mask :: Effect where
   Mask :: ((m ~> m) -> m a) -> Mask m a
   UninterruptibleMask :: ((m ~> m) -> m a) -> Mask m a
-  Bracket :: m a -> (a -> m c) -> (a -> m b) -> Mask m b
-  BracketOnError :: m a -> (a -> m c) -> (a -> m b) -> Mask m b
+  OnException :: m a -> m b -> Mask m a
 
 -- * Operations
 
-makeEffect ''Mask
+makeEffect_ ''Mask
 
+-- | Prevents a computation from receiving asynchronous exceptions, i.e. being interrupted by another thread. Also
+-- provides a function to restore receiving async exceptions for a computation.
+--
+-- However, some potentially blocking actions like @takeMVar@ can still be interrupted, and for them also not to be
+-- interrupted in any case you'll need 'uninterruptibleMask'. See 'Control.Exception.mask' for details.
+mask :: Mask :> es => ((Eff es ~> Eff es) -> Eff es a) -> Eff es a
+
+-- | Prevents a computation from receiving asynchronous exceptions, even if there is an interruptible operation
+-- (operations that potentially deadlocks or otherwise blocks indefinitely). Therefore this function is potentially
+-- dangerous in the sense that it can make a thread both unresponsive and unkillable. See
+-- 'Control.Exception.uninterruptibleMask' for details.
+uninterruptibleMask :: Mask :> es => ((Eff es ~> Eff es) -> Eff es a) -> Eff es a
+
+-- | Like 'onError', but without 'uninterruptibleMask'ing the cleanup action, making it possible that a cleanup action
+-- is interrupted. Use 'onError' is usually the safer option.
+onException :: Mask :> es
+  => Eff es a -- ^ The main computation that may throw an exception
+  -> Eff es b -- ^ The computation that runs when an exception is thrown
+  -> Eff es a
+
+-- | Run a computation that acquires a resource (@alloc@), then a main computation using that resource, then a cleanup
+-- computation (@dealloc@). 'bracket' guarantees that @alloc@ and @dealloc@ will always run, regardless of whether an
+-- exception is thrown in the main computation. Note that if an exception is thrown in the main computation, it will
+-- be rethrown after 'bracket' finishes.
+--
+-- === Technical details
+--
+-- Note that this function uses @unliftio@ semantics: resource acquiring action is interruptibly 'mask'ed while
+-- resource cleanup is 'uninterruptibleMask'ed. Most of the times, this will be what you want. Other functions in this
+-- module use @unliftio@ semantics too.
+bracket :: Mask :> es
+  => Eff es a -- ^ The computation to run first, usually acquires a resource
+  -> (a -> Eff es c) -- ^ The computation to run after the main computation, usually cleans up
+  -> (a -> Eff es b) -- ^ The main computation that uses the resource
+  -> Eff es b
+bracket alloc dealloc action = mask \restore -> do
+  res <- alloc
+  ret <- restore (action res) `onError` dealloc res
+  _ <- uninterruptibleMask_ (dealloc res)
+  pure ret
+
+-- | Like 'bracket', but only runs cleanup if an exception is thrown in the main computation.
+bracketOnError :: Mask :> es
+  => Eff es a -- ^ The computation to run first, usually acquires a resource
+  -> (a -> Eff es c) -- ^ The computation to run when the main computation throws an exception, usually cleans up
+  -> (a -> Eff es b) -- ^ The main computation that uses the resource
+  -> Eff es b
+bracketOnError alloc dealloc action = mask \restore -> do
+  res <- alloc
+  restore (action res) `onError` dealloc res
+
 -- | Variant of 'mask' that does not provide a restoring function.
 mask_ :: Mask :> es => Eff es a -> Eff es a
 mask_ m = mask \_ -> m
@@ -44,21 +111,34 @@
 bracket_ :: Mask :> es => Eff es a -> Eff es c -> (a -> Eff es b) -> Eff es b
 bracket_ ma = bracket ma . const
 
--- | Attach a cleanup action that will always run to a potentially throwing computation.
-finally :: Mask :> es => Eff es a -> Eff es b -> Eff es a
-finally m mz = bracket_ (pure ()) mz (const m)
+-- | Variant of 'bracketOnError' that does not pass the allocated resource to the cleanup action.
+bracketOnError_ :: Mask :> es => Eff es a -> Eff es c -> (a -> Eff es b) -> Eff es b
+bracketOnError_ ma = bracketOnError ma . const
 
--- | Attach an action that runs if the main computation throws an exception.
-onError :: Mask :> es => Eff es a -> Eff es b -> Eff es a
-onError m mz = bracketOnError (pure ()) (const mz) (const m)
+-- | Attach an action that runs if the main computation throws an exception. Note that this will rethrow the exception
+-- instead of returning to normal control flow.
+--
+-- The cleanup action is guaranteed not to be interrupted halfways.
+onError :: Mask :> es
+  => Eff es a -- ^ The main computation that may throw an exception
+  -> Eff es b -- ^ The computation that runs when an exception is thrown
+  -> Eff es a
+onError m n = m `onException` uninterruptibleMask_ n
 
+-- | Attach a cleanup action that will always run after a potentially throwing computation.
+finally :: Mask :> es
+  => Eff es a -- ^ The main computation that may throw an exception
+  -> Eff es b -- ^ The computation that runs after the main computation, regardless of whether an exception is thrown
+  -> Eff es a
+finally m mz = (m `onError` mz) <* uninterruptibleMask_ mz
+
 -- * Interpretations
 
 -- | Interpret the 'Mask' effect in terms of primitive 'IO' actions.
 runMask :: Eff (Mask ': es) ~> Eff es
 runMask = thisIsPureTrustMe . reinterpret \case
-  Mask f                 -> withToIO \toIO -> Exc.mask \restore -> toIO $ f (fromIO . restore . toIO)
-  UninterruptibleMask f  -> withToIO \toIO -> Exc.uninterruptibleMask \restore -> toIO $ f (fromIO . restore . toIO)
-  Bracket ma mz m        -> withToIO \toIO -> Exc.bracket (toIO ma) (toIO . mz) (toIO . m)
-  BracketOnError ma mz m -> withToIO \toIO -> Exc.bracketOnError (toIO ma) (toIO . mz) (toIO . m)
+  Mask f -> withToIO \toIO -> Exc.mask \restore -> toIO $ f (fromIO . restore . toIO)
+  UninterruptibleMask f -> withToIO \toIO -> Exc.uninterruptibleMask \restore -> toIO $ f (fromIO . restore . toIO)
+  OnException m n -> withToIO \toIO -> toIO m `Exc.catch` \(e :: Exc.SomeException) ->
+    Exc.try @Exc.SomeException (toIO n) *> Exc.throwIO e
 {-# INLINE runMask #-}
diff --git a/src/Cleff/Output.hs b/src/Cleff/Output.hs
--- a/src/Cleff/Output.hs
+++ b/src/Cleff/Output.hs
@@ -8,10 +8,15 @@
 module Cleff.Output
   ( -- * Effect
     Output (..)
-  , -- * Operations
-    output
-  , -- * Interpretations
-    outputToListState, outputToWriter, ignoreOutput, runOutputEff, mapOutput, bindOutput
+    -- * Operations
+  , output
+    -- * Interpretations
+  , outputToListState
+  , outputToWriter
+  , ignoreOutput
+  , runOutputEff
+  , mapOutput
+  , bindOutput
   ) where
 
 import           Cleff
@@ -20,14 +25,17 @@
 
 -- * Effect
 
--- | An effect that is capable of sending outputs, for example to a log file or an output stream.
+-- | An effect that is capable of producing outputs, for example writing to a log file or an output stream.
 data Output o :: Effect where
   Output :: o -> Output o m ()
 
 -- * Operations
 
-makeEffect ''Output
+makeEffect_ ''Output
 
+-- | Produce an output value.
+output :: Output o :> es => o -> Eff es ()
+
 -- * Interpretations
 
 -- | Run an 'Output' effect by accumulating a list. Note that outputs are being prepended to the head of the list, so
@@ -63,7 +71,7 @@
   Output x -> output $ f x
 {-# INLINE mapOutput #-}
 
--- | Transform an 'Input' effect into another one already in the effect stack, by an effectful computation.
+-- | Transform an 'Output' effect into another one already in the effect stack, by an effectful computation.
 --
 -- @since 0.2.1.0
 bindOutput :: Output o' :> es => (o -> Eff es o') -> Eff (Output o ': es) ~> Eff es
diff --git a/src/Cleff/Reader.hs b/src/Cleff/Reader.hs
--- a/src/Cleff/Reader.hs
+++ b/src/Cleff/Reader.hs
@@ -8,10 +8,13 @@
 module Cleff.Reader
   ( -- * Effect
     Reader (..)
-  , -- * Operations
-    ask, local, asks
-  , -- * Interpretations
-    runReader, magnify
+    -- * Operations
+  , ask
+  , local
+  , asks
+    -- * Interpretations
+  , runReader
+  , magnify
   ) where
 
 import           Cleff
@@ -20,16 +23,25 @@
 -- * Effect
 
 -- | An effect capable of providing an immutable environment @r@ that can be read. This roughly corresponds to the
--- @MonadReader@ typeclass and @ReaderT@ monad transformer in the @mtl@ approach.
+-- @MonadReader@ typeclass and @ReaderT@ monad transformer in the @mtl@ library.
 data Reader r :: Effect where
   Ask :: Reader r m r
   Local :: (r -> r) -> m a -> Reader r m a
 
 -- * Operations
 
-makeEffect ''Reader
+makeEffect_ ''Reader
 
--- | Apply a function on the result of 'ask'.
+-- | Obtain the environment value.
+ask :: Reader r :> es => Eff es r
+
+-- | Modify the environment value temporarily for a computation.
+local :: Reader r :> es
+  => (r -> r) -- ^ The function that modifies the environment
+  -> Eff es a -- ^ The computation to run with the modified environment
+  -> Eff es a
+
+-- | Apply a function to the result of 'ask'.
 asks :: Reader r :> es => (r -> s) -> Eff es s
 asks = (<$> ask)
 
diff --git a/src/Cleff/State.hs b/src/Cleff/State.hs
--- a/src/Cleff/State.hs
+++ b/src/Cleff/State.hs
@@ -8,10 +8,18 @@
 module Cleff.State
   ( -- * Effect
     State (..)
-  , -- * Operations
-    get, put, state, gets, modify
-  , -- * Interpretations
-    runState, runStateIORef, runStateMVar, runStateTVar, zoom
+    -- * Operations
+  , get
+  , put
+  , state
+  , gets
+  , modify
+    -- * Interpretations
+  , runState
+  , runStateIORef
+  , runStateMVar
+  , runStateTVar
+  , zoom
   ) where
 
 import           Cleff
@@ -27,7 +35,7 @@
 -- * Effect
 
 -- | An effect capable of providing a mutable state @s@ that can be read and written. This roughly corresponds to the
--- @MonadState@ typeclass and @StateT@ monad transformer in the @mtl@ approach.
+-- @MonadState@ typeclass and @StateT@ monad transformer in the @mtl@ library.
 data State s :: Effect where
   Get :: State s m s
   Put :: s -> State s m ()
@@ -35,8 +43,19 @@
 
 -- * Operations
 
-makeEffect ''State
+makeEffect_ ''State
 
+-- | Read the current state.
+get :: State s :> es => Eff es s
+
+-- | Update the state with a new value.
+put :: State s :> es => s -> Eff es ()
+
+-- | Modify the state /and/ produce a value from the state via a function.
+state :: State s :> es
+  => (s -> (a, s)) -- ^ The function that takes the state and returns a result value together with a modified state
+  -> Eff es a
+
 -- | Apply a function to the result of 'get'.
 gets :: State s :> es => (s -> t) -> Eff es t
 gets = (<$> get)
@@ -56,7 +75,9 @@
 
 -- | Run the 'State' effect.
 --
--- __Caveat__: The 'runState' interpreter is implemented with 'Data.IORef.IORef's and there is no way to do arbitrary
+-- === Caveats
+--
+-- The 'runState' interpreter is implemented with 'Data.IORef.IORef's and there is no way to do arbitrary
 -- atomic transactions. The 'state' operation is atomic though and it is implemented with 'atomicModifyIORefCAS', which
 -- can be faster than @atomicModifyIORef@ in contention. For any more complicated cases of atomicity, please build your
 -- own effect that uses either @MVar@s or @TVar@s based on your need.
diff --git a/src/Cleff/Trace.hs b/src/Cleff/Trace.hs
--- a/src/Cleff/Trace.hs
+++ b/src/Cleff/Trace.hs
@@ -8,10 +8,14 @@
 module Cleff.Trace
   ( -- * Effect
     Trace (..)
-  , -- * Operations
-    trace
-  , -- * Interpretations
-    runTraceHandle, runTraceStdout, runTraceStderr, ignoreTrace, traceToOutput
+    -- * Operations
+  , trace
+    -- * Interpretations
+  , runTraceHandle
+  , runTraceStdout
+  , runTraceStderr
+  , ignoreTrace
+  , traceToOutput
   ) where
 
 import           Cleff
@@ -26,7 +30,10 @@
 
 -- * Operations
 
-makeEffect ''Trace
+makeEffect_ ''Trace
+
+-- | Output a trace message.
+trace :: Trace :> es => String -> Eff es ()
 
 -- * Interpretations
 
diff --git a/src/Cleff/Writer.hs b/src/Cleff/Writer.hs
--- a/src/Cleff/Writer.hs
+++ b/src/Cleff/Writer.hs
@@ -8,10 +8,13 @@
 module Cleff.Writer
   ( -- * Effect
     Writer (..)
-  , -- * Operations
-    tell, listen, listens
-  , -- * Interpretations
-    runWriter, runWriterBatch
+    -- * Operations
+  , tell
+  , listen
+  , listens
+    -- * Interpretations
+  , runWriter
+  , runWriterBatch
   ) where
 
 import           Cleff
@@ -22,8 +25,8 @@
 
 -- * Effect
 
--- | An effect capable of accumulating outputs. This roughly corresponds to the @MonadWriter@ typeclass and @WriterT@
--- monad transformer in the @mtl@ approach.
+-- | An effect capable of accumulating monoidal outputs. This roughly corresponds to the @MonadWriter@ typeclass and
+-- @WriterT@ monad transformer in the @mtl@ library.
 --
 -- However, note that this does not have a @pass@ operation as we are not sure what its semantics should be. In fact,
 -- the @pass@ semantics in @mtl@ is also unclear and will change when handlers are put in different orders. To avoid
@@ -34,8 +37,14 @@
 
 -- * Operations
 
-makeEffect ''Writer
+makeEffect_ ''Writer
 
+-- | Produces an output that is appended to the accumulated value.
+tell :: Writer w :> es => w -> Eff es ()
+
+-- | Monitor the output of a computation, and return the output alongside the computation's result.
+listen :: Writer w :> es => Eff es a -> Eff es (a, w)
+
 -- | Apply a function to the accumulated output of 'listen'.
 listens :: Writer w :> es => (w -> x) -> Eff es a -> Eff es (a, x)
 listens f m = do
@@ -46,7 +55,9 @@
 
 -- | Run a monoidal 'Writer' effect.
 --
--- __Caveat__: Both 'runWriter' and 'listen's under 'runWriter' will stop taking care of writer operations done on
+-- === Caveats
+--
+-- Both 'runWriter' and 'listen's under 'runWriter' will stop taking care of writer operations done on
 -- forked threads as soon as the main thread finishes its computation. Any writer operation done
 -- /before main thread finishes/ is still taken into account.
 runWriter :: ∀ w es a. Monoid w => Eff (Writer w ': es) a -> Eff es (a, w)
