Skip to content

Commit 7874960

Browse files
authored
Cleaner Monad{Throw,Catch,Mask} implementation (#312)
1 parent 7ece9dc commit 7874960

File tree

2 files changed

+74
-71
lines changed

2 files changed

+74
-71
lines changed

effectful-core/src/Effectful/Dispatch/Static/Unsafe.hs

Lines changed: 0 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -5,50 +5,3 @@ module Effectful.Dispatch.Static.Unsafe
55
) where
66

77
import Effectful.Internal.Monad
8-
9-
-- | Utility for lifting 'IO' computations of type
10-
--
11-
-- @'IO' a -> 'IO' b@
12-
--
13-
-- to
14-
--
15-
-- @'Eff' es a -> 'Eff' es b@
16-
--
17-
-- This function is __really unsafe__ because:
18-
--
19-
-- - It can be used to introduce arbitrary 'IO' actions into pure 'Eff'
20-
-- computations.
21-
--
22-
-- - The 'IO' computation must run its argument in a way that's perceived as
23-
-- sequential to the outside observer, e.g. in the same thread or in a worker
24-
-- thread that finishes before the argument is run again.
25-
--
26-
-- __Warning:__ if you disregard the second point, you will experience weird
27-
-- bugs, data races or internal consistency check failures.
28-
--
29-
-- When in doubt, use 'Effectful.Dispatch.Static.unsafeLiftMapIO', especially
30-
-- since this version saves only a simple safety check per call of
31-
-- @reallyUnsafeLiftMapIO f@.
32-
reallyUnsafeLiftMapIO :: (IO a -> IO b) -> Eff es a -> Eff es b
33-
reallyUnsafeLiftMapIO f m = unsafeEff $ \es -> f (unEff m es)
34-
35-
-- | Create an unlifting function.
36-
--
37-
-- This function is __really unsafe__ because:
38-
--
39-
-- - It can be used to introduce arbitrary 'IO' actions into pure 'Eff'
40-
-- computations.
41-
--
42-
-- - Unlifted 'Eff' computations must be run in a way that's perceived as
43-
-- sequential to the outside observer, e.g. in the same thread as the caller
44-
-- of 'reallyUnsafeUnliftIO' or in a worker thread that finishes before
45-
-- another unlifted computation is run.
46-
--
47-
-- __Warning:__ if you disregard the second point, you will experience weird
48-
-- bugs, data races or internal consistency check failures.
49-
--
50-
-- When in doubt, use 'Effectful.Dispatch.Static.unsafeSeqUnliftIO', especially
51-
-- since this version saves only a simple safety check per call of the unlifting
52-
-- function.
53-
reallyUnsafeUnliftIO :: ((forall r. Eff es r -> IO r) -> IO a) -> Eff es a
54-
reallyUnsafeUnliftIO k = unsafeEff $ \es -> k (`unEff` es)

effectful-core/src/Effectful/Internal/Monad.hs

Lines changed: 74 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ module Effectful.Internal.Monad
4747
, withSeqEffToIO
4848
, withEffToIO
4949
, withConcEffToIO
50+
, reallyUnsafeLiftMapIO
51+
, reallyUnsafeUnliftIO
5052

5153
-- ** Low-level unlifts
5254
, seqUnliftIO
@@ -261,6 +263,53 @@ concUnliftIO es Ephemeral Unlimited = ephemeralConcUnlift es maxBound
261263
concUnliftIO es Persistent (Limited threads) = persistentConcUnlift es False threads
262264
concUnliftIO es Persistent Unlimited = persistentConcUnlift es True maxBound
263265

266+
-- | Utility for lifting 'IO' computations of type
267+
--
268+
-- @'IO' a -> 'IO' b@
269+
--
270+
-- to
271+
--
272+
-- @'Eff' es a -> 'Eff' es b@
273+
--
274+
-- This function is __really unsafe__ because:
275+
--
276+
-- - It can be used to introduce arbitrary 'IO' actions into pure 'Eff'
277+
-- computations.
278+
--
279+
-- - The 'IO' computation must run its argument in a way that's perceived as
280+
-- sequential to the outside observer, e.g. in the same thread or in a worker
281+
-- thread that finishes before the argument is run again.
282+
--
283+
-- __Warning:__ if you disregard the second point, you will experience weird
284+
-- bugs, data races or internal consistency check failures.
285+
--
286+
-- When in doubt, use 'Effectful.Dispatch.Static.unsafeLiftMapIO', especially
287+
-- since this version saves only a simple safety check per call of
288+
-- @reallyUnsafeLiftMapIO f@.
289+
reallyUnsafeLiftMapIO :: (IO a -> IO b) -> Eff es a -> Eff es b
290+
reallyUnsafeLiftMapIO f m = unsafeEff $ \es -> f (unEff m es)
291+
292+
-- | Create an unlifting function.
293+
--
294+
-- This function is __really unsafe__ because:
295+
--
296+
-- - It can be used to introduce arbitrary 'IO' actions into pure 'Eff'
297+
-- computations.
298+
--
299+
-- - Unlifted 'Eff' computations must be run in a way that's perceived as
300+
-- sequential to the outside observer, e.g. in the same thread as the caller
301+
-- of 'reallyUnsafeUnliftIO' or in a worker thread that finishes before
302+
-- another unlifted computation is run.
303+
--
304+
-- __Warning:__ if you disregard the second point, you will experience weird
305+
-- bugs, data races or internal consistency check failures.
306+
--
307+
-- When in doubt, use 'Effectful.Dispatch.Static.unsafeSeqUnliftIO', especially
308+
-- since this version saves only a simple safety check per call of the unlifting
309+
-- function.
310+
reallyUnsafeUnliftIO :: ((forall r. Eff es r -> IO r) -> IO a) -> Eff es a
311+
reallyUnsafeUnliftIO k = unsafeEff $ \es -> k (`unEff` es)
312+
264313
----------------------------------------
265314
-- Base
266315

@@ -308,39 +357,40 @@ instance NonDet :> es => MonadPlus (Eff es)
308357
-- Exception
309358

310359
instance C.MonadThrow (Eff es) where
311-
throwM = unsafeEff_ . E.throwIO
360+
throwM = unsafeEff_ . withFrozenCallStack E.throwIO
312361

313362
instance C.MonadCatch (Eff es) where
314-
catch m handler = unsafeEff $ \es -> do
315-
unEff m es `E.catch` \e -> do
316-
unEff (handler e) es
363+
catch action handler = reallyUnsafeUnliftIO $ \unlift -> do
364+
E.catch (unlift action) (unlift . handler)
317365

318366
instance C.MonadMask (Eff es) where
319-
mask k = unsafeEff $ \es -> E.mask $ \unmask ->
320-
unEff (k $ \m -> unsafeEff $ unmask . unEff m) es
367+
mask k = reallyUnsafeUnliftIO $ \unlift -> do
368+
E.mask $ \release -> unlift $ k (reallyUnsafeLiftMapIO release)
321369

322-
uninterruptibleMask k = unsafeEff $ \es -> E.uninterruptibleMask $ \unmask ->
323-
unEff (k $ \m -> unsafeEff $ unmask . unEff m) es
370+
uninterruptibleMask k = reallyUnsafeUnliftIO $ \unlift -> do
371+
E.uninterruptibleMask $ \release -> unlift $ k (reallyUnsafeLiftMapIO release)
324372

325-
generalBracket acquire release use = unsafeEff $ \es -> E.mask $ \unmask -> do
326-
resource <- unEff acquire es
373+
generalBracket before after action = reallyUnsafeUnliftIO $ \unlift -> do
374+
E.mask $ \unmask -> do
375+
a <- unlift before
327376
#if MIN_VERSION_base(4,21,0)
328-
b <- E.catchNoPropagate
329-
(unmask (unEff (use resource) es))
330-
(\ec@(E.ExceptionWithContext _ e) -> do
331-
_ <- unEff (release resource $ C.ExitCaseException e) es
332-
E.rethrowIO ec
333-
)
377+
b <- E.catchNoPropagate
378+
(unmask . unlift $ action a)
379+
(\ec@(E.ExceptionWithContext _ e) -> do
380+
_ <- E.annotateIO (E.WhileHandling (E.toException ec)) $ do
381+
unlift . after a $ C.ExitCaseException e
382+
E.rethrowIO ec
383+
)
334384
#else
335-
b <- E.catch
336-
(unmask (unEff (use resource) es))
337-
(\e -> do
338-
_ <- unEff (release resource $ C.ExitCaseException e) es
339-
E.throwIO e
340-
)
385+
b <- E.catch
386+
(unmask . unlift $ action a)
387+
(\e -> do
388+
_ <- unlift . after a $ C.ExitCaseException e
389+
E.throwIO e
390+
)
341391
#endif
342-
c <- unEff (release resource $ C.ExitCaseSuccess b) es
343-
pure (b, c)
392+
c <- unlift . after a $ C.ExitCaseSuccess b
393+
pure (b, c)
344394

345395
----------------------------------------
346396
-- Fail

0 commit comments

Comments
 (0)