on master help sign in
Provide a complete set of (un)lifts for dealing with local operations
commit91e64bfc23e91ccbcfd80e94f6cff293bca29d1f authorAndrzej Rybczak <andrzej@rybczak.net> dateMon 12 Jul 2021, 17:14 (5 years ago) parentd69f65d
7 files changed+182−92
bench/Concurrency.hs+62−28 bench/Unlift.hs+1−1 src/Effectful.hs+14−10 src/Effectful/Async.hs+4−4 src/Effectful/Internal/Monad.hs+6−7 src/Effectful/Internal/Unlift.hs+1−1 src/Effectful/Interpreter.hs+94−41
bench/Concurrency.hs+62−28
@@ -20,57 +20,91 @@ import Utils
2020
2121concurrencyBenchmark :: Benchmark
2222concurrencyBenchmark = bgroup "concurrency"
23− [ bench "async (IO)" $ nfAppIO asyncBenchIO (pure 1)
24− , bgroup "shallow"
25− [ bench "async (Eff)" $ nfAppIO (runShallow . A.runAsyncE . asyncBench) (pure 1)
26− , bench "Fork (1)" $ nfAppIO (runShallow . runFork1 . forkBench) (pure 1)
27− , bench "Fork (2)" $ nfAppIO (runShallow . runFork2 . forkBench) (pure 1)
28− ]
29− , bgroup "deep"
30− [ bench "async (Eff)" $ nfAppIO (runDeep . A.runAsyncE . asyncBench) (pure 1)
31− , bench "Fork (1)" $ nfAppIO (runDeep . runFork1 . forkBench) (pure 1)
32− , bench "Fork (2)" $ nfAppIO (runDeep . runFork2 . forkBench) (pure 1)
33− ]
23+ [ bgroup "shallow" $ map shallowBench [1, 10, 100]
24+ , bgroup "deep" $ map deepBench [1, 10, 100]
3425 ]
3526
27+shallowBench :: Int -> Benchmark
28+shallowBench n = bgroup ("unmask " ++ show n ++ "x")
29+ [ bench "async (IO)" $ nfAppIO (asyncBenchIO n) op
30+ , bench "async (Eff)" $ nfAppIO (runShallow . A.runAsyncE . asyncBench n) op
31+ , bench "Fork (localUnliftIO/withLiftMapIO)" $
32+ nfAppIO (runShallow . runFork1 . forkBench n) op
33+ , bench "Fork (localUnlift/withLiftMap)" $
34+ nfAppIO (runShallow . runFork2 . forkBench n) op
35+ , bench "Fork (localLiftUnliftIO)" $
36+ nfAppIO (runShallow . runFork3 . forkBench n) op
37+ , bench "Fork (localLiftUnlift)" $
38+ nfAppIO (runShallow . runFork4 . forkBench n) op
39+ ]
40+
41+deepBench :: Int -> Benchmark
42+deepBench n = bgroup ("unmask " ++ show n ++ "x")
43+ [ bench "async (Eff)" $ nfAppIO (runDeep . A.runAsyncE . asyncBench n) op
44+ , bench "Fork (localUnliftIO/withLiftMapIO)" $
45+ nfAppIO (runDeep . runFork1 . forkBench n) op
46+ , bench "Fork (localUnlift/withLiftMap)" $
47+ nfAppIO (runDeep . runFork2 . forkBench n) op
48+ , bench "Fork (localLiftUnliftIO)" $
49+ nfAppIO (runDeep . runFork3 . forkBench n) op
50+ , bench "Fork (localLiftUnlift)" $
51+ nfAppIO (runDeep . runFork4 . forkBench n) op
52+ ]
53+
54+op :: Monad m => m Int
55+op = pure 1
56+
3657----------------------------------------
3758
3859data Fork :: Effect where
3960 ForkWithUnmask :: ((forall a. m a -> m a) -> m r) -> Fork m (Async r)
4061
41−-- | Uses 'localUnliftIO' and 'withLiftSeqOp'.
62+-- | Uses 'localUnliftIO' and 'withLiftMapIO'.
4263runFork1 :: IOE :> es => Eff (Fork : es) a -> Eff es a
43−runFork1 = interpretM $ \env (ForkWithUnmask m) -> withLiftSeqOp $ \liftSeqOp -> do
44− localUnliftIO env (ConcUnlift Ephemeral $ Limited 1) $ \unlift -> do
45− asyncWithUnmask $ \unmask -> unlift $ m $ liftSeqOp unmask
64+runFork1 = interpretM $ \env -> \case
65+ ForkWithUnmask m -> withLiftMapIO $ \liftMap -> do
66+ localUnliftIO env (ConcUnlift Ephemeral $ Limited 1) $ \unlift -> do
67+ asyncWithUnmask $ \unmask -> unlift $ m $ liftMap unmask
4668
47−-- | Uses 'localLiftUnliftIO', slower than (1).
69+-- | Uses 'localUnlift' and 'withLiftMap'.
4870runFork2 :: IOE :> es => Eff (Fork : es) a -> Eff es a
49−runFork2 = interpretM $ \env (ForkWithUnmask m) -> do
50− localLiftUnliftIO env (ConcUnlift Persistent $ Limited 1) $ \lift unlift -> do
51− asyncWithUnmask $ \unmask -> unlift $ m $ lift . unmask . unlift
71+runFork2 = reinterpretM A.runAsyncE $ \env -> \case
72+ ForkWithUnmask m -> withLiftMap $ \liftMap -> do
73+ localUnlift env (ConcUnlift Ephemeral $ Limited 1) $ \unlift -> do
74+ A.asyncWithUnmask $ \unmask -> unlift $ m $ liftMap unmask
5275
53−----------------------------------------
76+-- | Uses 'localLiftUnliftIO'.
77+runFork3 :: IOE :> es => Eff (Fork : es) a -> Eff es a
78+runFork3 = interpretM $ \env -> \case
79+ ForkWithUnmask m -> do
80+ localLiftUnliftIO env (ConcUnlift Persistent $ Limited 1) $ \lift unlift -> do
81+ asyncWithUnmask $ \unmask -> unlift $ m $ lift . unmask . unlift
5482
55−n :: Int
56−n = 100
83+-- | Uses 'localLiftUnlift'.
84+runFork4 :: IOE :> es => Eff (Fork : es) a -> Eff es a
85+runFork4 = reinterpretM A.runAsyncE $ \env -> \case
86+ ForkWithUnmask m -> do
87+ localLiftUnlift env (ConcUnlift Persistent $ Limited 1) $ \lift unlift -> do
88+ A.asyncWithUnmask $ \unmask -> unlift $ m $ lift . unmask . unlift
89+
90+----------------------------------------
5791
58−asyncBenchIO :: IO Int -> IO Int
59−asyncBenchIO f = do
92+asyncBenchIO :: Int -> IO Int -> IO Int
93+asyncBenchIO n f = do
6094 a <- asyncWithUnmask $ \unmask -> do
6195 sum <$> replicateM n (unmask f)
6296 wait a
6397{-# NOINLINE asyncBenchIO #-}
6498
65−asyncBench :: A.AsyncE :> es => Eff es Int -> Eff es Int
66−asyncBench f = do
99+asyncBench :: A.AsyncE :> es => Int -> Eff es Int -> Eff es Int
100+asyncBench n f = do
67101 a <- A.asyncWithUnmask $ \unmask -> do
68102 sum <$> replicateM n (unmask f)
69103 A.wait a
70104{-# NOINLINE asyncBench #-}
71105
72−forkBench :: (IOE :> es, Fork :> es) => Eff es Int -> Eff es Int
73−forkBench f = do
106+forkBench :: (IOE :> es, Fork :> es) => Int -> Eff es Int -> Eff es Int
107+forkBench n f = do
74108 a <- send $ ForkWithUnmask $ \unmask -> do
75109 sum <$> replicateM n (unmask f)
76110 liftIO $ wait a
bench/Unlift.hs+1−1
@@ -18,7 +18,7 @@ import Utils
1818import qualified Effectful.Async as A
1919
2020unliftBenchmark :: Benchmark
21−unliftBenchmark = bgroup "unlift"
21+unliftBenchmark = bgroup "unlifting"
2222 [ bgroup "reference"
2323 [ bench "dummy" $ nfIO benchDummy
2424 , bench "async" $ nfIO benchAsync
src/Effectful.hs+14−10
@@ -6,9 +6,6 @@ module Effectful
66 , Effect
77 , (:>)
88
9− -- ** Sending effects
10− , send
11−
129 -- * Running the 'Eff' monad
1310
1411 -- ** Pure operations
@@ -25,7 +22,10 @@ module Effectful
2522 , unliftStrategy
2623 , withUnliftStrategy
2724
28− -- * Building 'Effect' handlers
25+ -- * 'Effect' handlers
26+
27+ -- ** Sending operations to the handler
28+ , send
2929
3030 -- ** Basic handlers
3131 , interpret
@@ -35,18 +35,22 @@ module Effectful
3535 , reinterpret
3636 , reinterpretM
3737
38− -- ** Local operations
38+ -- ** Handling local 'Eff' operations
3939 , LocalEnv
4040
41− -- *** 'Eff'
41+ -- *** Unlifts
4242 , localSeqUnlift
43− , localUnlift
44−
45− -- *** 'IO'
4643 , localSeqUnliftIO
44+ , localUnlift
4745 , localUnliftIO
46+
47+ -- *** Lifts
48+ , withLiftMap
49+ , withLiftMapIO
50+
51+ -- *** Bidirectional lifts
52+ , localLiftUnlift
4853 , localLiftUnliftIO
49− , withLiftSeqOp
5054 ) where
5155
5256import Effectful.Interpreter
src/Effectful/Async.hs+4−4
@@ -532,9 +532,9 @@ liftAsyncWithUnmask
532532 -> ((forall b. Eff es b -> Eff es b) -> Eff es a)
533533 -> Eff es (Async a)
534534liftAsyncWithUnmask fork action = do
535− unsafeWithLiftSeqOp $ \liftSeqOp -> unsafeEff $ \es -> do
535+ unsafeWithLiftMapIO $ \liftMap -> unsafeEff $ \es -> do
536536 esA <- cloneEnv es
537− fork $ \unmask -> unEff (action $ liftSeqOp unmask) esA
537+ fork $ \unmask -> unEff (action $ liftMap unmask) esA
538538
539539liftWithAsync
540540 :: (IO a -> (Async a -> IO b) -> IO b)
@@ -552,7 +552,7 @@ liftWithAsyncWithUnmask
552552 -> (Async a -> Eff es b)
553553 -> Eff es b
554554liftWithAsyncWithUnmask withA action k = do
555− unsafeWithLiftSeqOp $ \liftSeqOp -> unsafeEff $ \es -> do
555+ unsafeWithLiftMapIO $ \liftMap -> unsafeEff $ \es -> do
556556 esA <- cloneEnv es
557− withA (\unmask -> unEff (action $ liftSeqOp unmask) esA)
557+ withA (\unmask -> unEff (action $ liftMap unmask) esA)
558558 (\a -> unEff (k a) es)
src/Effectful/Internal/Monad.hs+6−7
@@ -35,7 +35,7 @@ module Effectful.Internal.Monad
3535 , withUnliftStrategy
3636
3737 --- *** Low-level helpers
38− , unsafeWithLiftSeqOp
38+ , unsafeWithLiftMapIO
3939 , unsafeSeqUnliftEff
4040 , unsafeConcUnliftEff
4141
@@ -142,10 +142,10 @@ unsafeEff_ m = unsafeEff $ \_ -> m
142142--
143143-- /Note:/ the 'IO' operation must not run its argument in a separate thread,
144144-- attempting to do so will result in a runtime error.
145−unsafeWithLiftSeqOp
145+unsafeWithLiftMapIO
146146 :: ((forall a b localEs. (IO a -> IO b) -> Eff localEs a -> Eff localEs b) -> Eff es r)
147147 -> Eff es r
148−unsafeWithLiftSeqOp k = k $ \mapIO m -> unsafeEff $ \es -> do
148+unsafeWithLiftMapIO k = k $ \mapIO m -> unsafeEff $ \es -> do
149149 unsafeSeqUnliftEff es $ \unlift -> mapIO $ unlift m
150150
151151-- | Lower 'Eff' operations into 'IO' ('SeqUnlift').
@@ -272,8 +272,7 @@ tryFailIO tag m =
272272----------------------------------------
273273-- IO
274274
275−-- | Run arbitrary 'IO' operations via 'MonadIO', 'MonadUnliftIO' or
276−-- 'MonadBaseControl' 'IO'.
275+-- | Run arbitrary 'IO' operations via 'MonadIO' or 'MonadUnliftIO'.
277276newtype IOE :: Effect where
278277 IOE :: UnliftStrategy -> IOE m r
279278
@@ -319,8 +318,8 @@ instance Prim :> es => PrimMonad (Eff es) where
319318----------------------------------------
320319-- Unlift strategies
321320
322−-- | The strategy to use when unlifting 'Eff' operations via 'withRunInIO',
323−-- 'liftBaseWith' or the 'Effectful.Interpreter.localUnlift' family.
321+-- | The strategy to use when unlifting 'Eff' operations via 'withRunInIO' or
322+-- the 'Effectful.Interpreter.localUnlift' family.
324323data UnliftStrategy
325324 = SeqUnlift
326325 -- ^ The fastest strategy and a default setting for 'IOE'. An attempt to call
src/Effectful/Internal/Unlift.hs+1−1
@@ -1,7 +1,7 @@
11{-# LANGUAGE MagicHash #-}
22{-# LANGUAGE UnboxedTuples #-}
33{-# OPTIONS_HADDOCK not-home #-}
4−-- | Implementation of the concurrent unlift.
4+-- | Implementation of concurrent unlifts.
55--
66-- This module is intended for internal use only, and may change without warning
77-- in subsequent releases.
src/Effectful/Interpreter.hs+94−41
@@ -1,5 +1,5 @@
11module Effectful.Interpreter
2− ( -- * Sending commands
2+ ( -- * Sending operations to the handler
33 send
44
55 -- * Handling effects
@@ -12,21 +12,26 @@ module Effectful.Interpreter
1212 , reinterpret
1313 , reinterpretM
1414
15− -- ** Local operations
15+ -- ** Handling local 'Eff' operations
1616 , LocalEnv
1717
18− -- *** 'Eff'
18+ -- *** Unlifts
1919 , localSeqUnlift
20− , localUnlift
21−
22− -- *** 'IO'
2320 , localSeqUnliftIO
21+ , localUnlift
2422 , localUnliftIO
23+
24+ -- *** Lifts
25+ , withLiftMap
26+ , withLiftMapIO
27+
28+ -- *** Bidirectional lifts
29+ , localLiftUnlift
2530 , localLiftUnliftIO
26− , withLiftSeqOp
2731 ) where
2832
2933import Control.Exception
34+import Control.Monad.IO.Unlift
3035import GHC.Stack
3136
3237import Effectful.Internal.Effect
@@ -64,7 +69,7 @@ runInterpreter es0 e m = do
6469----------------------------------------
6570-- Sending commands
6671
67−-- | Send an operation of a given effect to the interpreter.
72+-- | Send an operation of a given effect to the interpreter for execution.
6873send :: (HasCallStack, e :> es) => e (Eff es) a -> Eff es a
6974send op = unsafeEff $ \es -> do
7075 Interpreter{..} <- getEnv es
@@ -129,7 +134,7 @@ reinterpretM runHandlerEs interpreter m = unsafeEff $ \es -> do
129134 runInterpreter es (Interpreter les interpreter) m
130135
131136----------------------------------------
132−-- Unlifting
137+-- Unlifts
133138
134139-- | Create a local unlifting function with the 'SeqUnlift' strategy. For the
135140-- general version see 'localUnlift'.
@@ -139,8 +144,7 @@ localSeqUnlift
139144 -> ((forall r. Eff localEs r -> Eff es r) -> Eff es a)
140145 -- ^ Continuation with the unlifting function in scope.
141146 -> Eff es a
142−localSeqUnlift (LocalEnv les) k = unsafeEff $ \es -> do
143− unsafeSeqUnliftEff les $ \unlift -> unEff (k $ unsafeEff_ . unlift) es
147+localSeqUnlift env = localUnlift env SeqUnlift
144148
145149-- | Create a local unlifting function with the 'SeqUnlift' strategy. For the
146150-- general version see 'localUnliftIO'.
@@ -151,7 +155,7 @@ localSeqUnliftIO
151155 -> ((forall r. Eff localEs r -> IO r) -> IO a)
152156 -- ^ Continuation with the unlifting function in scope.
153157 -> Eff es a
154−localSeqUnliftIO (LocalEnv les) k = unsafeEff_ $ unsafeSeqUnliftEff les k
158+localSeqUnliftIO env = localUnliftIO env SeqUnlift
155159
156160-- | Create a local unlifting function with the given strategy.
157161localUnlift
@@ -161,11 +165,13 @@ localUnlift
161165 -> ((forall r. Eff localEs r -> Eff es r) -> Eff es a)
162166 -- ^ Continuation with the unlifting function in scope.
163167 -> Eff es a
164−localUnlift (LocalEnv les) strategy k = unsafeEff $ \es -> case strategy of
165− SeqUnlift -> unsafeSeqUnliftEff les $ \unlift -> do
166− unEff (k $ unsafeEff_ . unlift) es
167− ConcUnlift p l -> unsafeConcUnliftEff les p l $ \unlift -> do
168− unEff (k $ unsafeEff_ . unlift) es
168+localUnlift (LocalEnv les) strategy k = case strategy of
169+ SeqUnlift -> unsafeEff $ \es -> do
170+ unsafeSeqUnliftEff les $ \unlift -> do
171+ (`unEff` es) $ k $ unsafeEff_ . unlift
172+ ConcUnlift p l -> unsafeEff $ \es -> do
173+ unsafeConcUnliftEff les p l $ \unlift -> do
174+ (`unEff` es) $ k $ unsafeEff_ . unlift
169175
170176-- | Create a local unlifting function with the given strategy.
171177localUnliftIO
@@ -176,29 +182,28 @@ localUnliftIO
176182 -> ((forall r. Eff localEs r -> IO r) -> IO a)
177183 -- ^ Continuation with the unlifting function in scope.
178184 -> Eff es a
179−localUnliftIO (LocalEnv les) unlift k = unsafeEff_ $ case unlift of
180− SeqUnlift -> unsafeSeqUnliftEff les k
181− ConcUnlift p l -> unsafeConcUnliftEff les p l k
185+localUnliftIO (LocalEnv les) strategy k = case strategy of
186+ SeqUnlift -> liftIO $ unsafeSeqUnliftEff les k
187+ ConcUnlift p l -> liftIO $ unsafeConcUnliftEff les p l k
182188
183−-- | Create a local unlifting function with the given strategy along with an
184−-- unrestricted lifting function.
189+-- | Utility for lifting 'Eff' operations of type
185190--
186−-- Useful for lifting 'IO' operations where the monadic action shows in both
187−-- positive (as a result) and negative (as an argument) position.
191+-- @'Eff' es a -> 'Eff' es b@
188192--
189−-- /Note:/ depending on the operations you're lifting, 'localUnliftIO' along
190−-- with 'withLiftSeqOp' might be enough and is more efficient.
191−localLiftUnliftIO
193+-- to
194+--
195+-- @forall localEs. 'Eff' localEs a -> 'Eff' localEs b@
196+--
197+-- /Note:/ the operation must not run its argument in a separate thread,
198+-- attempting to do so will result in a runtime error.
199+withLiftMap
192200 :: IOE :> es
193− => LocalEnv localEs
194− -- ^ Local environment.
195− -> UnliftStrategy
196− -> ((forall r. IO r -> Eff localEs r) -> (forall r. Eff localEs r -> IO r) -> IO a)
197− -- ^ Continuation with the lifting and unlifting function in scope.
198− -> Eff es a
199−localLiftUnliftIO (LocalEnv les) unlift k = unsafeEff_ $ case unlift of
200− SeqUnlift -> unsafeSeqUnliftEff les $ k unsafeEff_
201− ConcUnlift p l -> unsafeConcUnliftEff les p l $ k unsafeEff_
201+ => ((forall a b localEs. (Eff es a -> Eff es b) -> Eff localEs a -> Eff localEs b) -> Eff es r)
202+ -- ^ Continuation with the lifting function in scope.
203+ -> Eff es r
204+withLiftMap k = unsafeWithLiftMapIO $ \liftMap -> do
205+ unsafeEff $ \es -> (`unEff` es) $ k $ \f -> do
206+ liftMap $ (`unEff` es) . f . liftIO
202207
203208-- | Utility for lifting 'IO' operations of type
204209--
@@ -208,7 +213,7 @@ localLiftUnliftIO (LocalEnv les) unlift k = unsafeEff_ $ case unlift of
208213--
209214-- @forall localEs. 'Eff' localEs a -> 'Eff' localEs b@
210215--
211−-- /Note:/ the 'IO' operation must not run its argument in a separate thread,
216+-- /Note:/ the operation must not run its argument in a separate thread,
212217-- attempting to do so will result in a runtime error.
213218--
214219-- Useful e.g. for lifting the unmasking function in
@@ -221,16 +226,64 @@ localLiftUnliftIO (LocalEnv les) unlift k = unsafeEff_ $ case unlift of
221226--
222227-- >>> :{
223228-- runFork :: IOE :> es => Eff (Fork : es) a -> Eff es a
224−-- runFork = interpretM $ \env (ForkWithUnmask m) -> withLiftSeqOp $ \liftSeqOp -> do
229+-- runFork = interpretM $ \env (ForkWithUnmask m) -> withLiftMapIO $ \liftMap -> do
225230-- localUnliftIO env (ConcUnlift Ephemeral $ Limited 1) $ \unlift -> do
226−-- forkIOWithUnmask $ \unmask -> unlift $ m $ liftSeqOp unmask
231+-- forkIOWithUnmask $ \unmask -> unlift $ m $ liftMap unmask
227232-- :}
228−withLiftSeqOp
233+withLiftMapIO
229234 :: IOE :> es
230235 => ((forall a b localEs. (IO a -> IO b) -> Eff localEs a -> Eff localEs b) -> Eff es r)
231236 -- ^ Continuation with the lifting function in scope.
232237 -> Eff es r
233−withLiftSeqOp = unsafeWithLiftSeqOp
238+withLiftMapIO = unsafeWithLiftMapIO
239+
240+----------------------------------------
241+-- Bidirectional lifts
242+
243+-- | Create a local lifting and unlifting function with the given strategy.
244+--
245+-- Useful for lifting complicated 'Eff' operations where the monadic action
246+-- shows in both positive (as a result) and negative (as an argument) position.
247+--
248+-- /Note:/ depending on the operation you're lifting 'localUnlift' along with
249+-- 'withLiftMap' might be enough and is more efficient.
250+localLiftUnlift
251+ :: IOE :> es
252+ => LocalEnv localEs
253+ -- ^ Local environment.
254+ -> UnliftStrategy
255+ -> ((forall r. Eff es r -> Eff localEs r) -> (forall r. Eff localEs r -> Eff es r) -> Eff es a)
256+ -- ^ Continuation with the lifting and unlifting function in scope.
257+ -> Eff es a
258+localLiftUnlift (LocalEnv les) strategy k = case strategy of
259+ SeqUnlift -> unsafeEff $ \es -> do
260+ unsafeSeqUnliftEff es $ \unliftEs -> do
261+ unsafeSeqUnliftEff les $ \unliftLocalEs -> do
262+ (`unEff` es) $ k (unsafeEff_ . unliftEs) (liftIO . unliftLocalEs)
263+ ConcUnlift p l -> unsafeEff $ \es -> do
264+ unsafeConcUnliftEff es p l $ \unliftEs -> do
265+ unsafeConcUnliftEff les p l $ \unliftLocalEs -> do
266+ (`unEff` es) $ k (unsafeEff_ . unliftEs) (liftIO . unliftLocalEs)
267+
268+-- | Create a local unlifting function with the given strategy along with an
269+-- unrestricted lifting function.
270+--
271+-- Useful for lifting complicated 'IO' operations where the monadic action shows
272+-- in both positive (as a result) and negative (as an argument) position.
273+--
274+-- /Note:/ depending on the operation you're lifting 'localUnliftIO' along with
275+-- 'withLiftMapIO' might be enough and is more efficient.
276+localLiftUnliftIO
277+ :: IOE :> es
278+ => LocalEnv localEs
279+ -- ^ Local environment.
280+ -> UnliftStrategy
281+ -> ((forall r. IO r -> Eff localEs r) -> (forall r. Eff localEs r -> IO r) -> IO a)
282+ -- ^ Continuation with the lifting and unlifting function in scope.
283+ -> Eff es a
284+localLiftUnliftIO (LocalEnv les) strategy k = case strategy of
285+ SeqUnlift -> liftIO $ unsafeSeqUnliftEff les $ k unsafeEff_
286+ ConcUnlift p l -> liftIO $ unsafeConcUnliftEff les p l $ k unsafeEff_
234287
235288-- $setup
236289-- >>> import Control.Concurrent