Skip to content

Commit 07efc60

Browse files
committed
wip
1 parent 8f21d7b commit 07efc60

File tree

1 file changed

+128
-58
lines changed

1 file changed

+128
-58
lines changed

src/Benign.hs

Lines changed: 128 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,17 @@ module Benign
4747
-- * Monads
4848
EvalM (..),
4949
withAlteringM,
50+
withAlteringM',
5051
withSettingM,
52+
withSettingM',
5153
unsafeSpanBenignM,
54+
unsafeSpanBenignM',
55+
56+
-- * Strategies
57+
Strat,
58+
lazy,
59+
whnf,
60+
nf,
5261
)
5362
where
5463

@@ -133,8 +142,8 @@ setLocalState vault = do
133142
tid <- myThreadId
134143
modifyMVar_ localStates (evaluate . Map.insert tid vault)
135144

136-
-- | @'withAltering' f g thing@ evaluates 'thing' with the local state's field
137-
-- `f` set by `g` in the style of 'Map.alter'.
145+
-- | @'withAltering' f g strat thing@ evaluates 'thing' with the local state's
146+
-- field `f` set by `g` in the style of 'Map.alter'.
138147
--
139148
-- Why do we need this? If we are to do some logging in pure code, we still need
140149
-- to know /where/ to log too. That is we need configuration. It is out of
@@ -161,20 +170,20 @@ setLocalState vault = do
161170
-- make much sense to modify a global state. The modification could happen at
162171
-- any time and in any order. The result would be quite ill-defined.
163172
--
164-
-- The reason why we need the 'Eval' constraint is that, as a direct consequence
173+
-- The reason why we need the 'strat' argument is that, as a direct consequence
165174
-- of the design, the lexical state is passed dynamically to `thing`. That is,
166175
-- the state that is seen by a piece of code depends on where it's executed: if
167176
-- a lazy thunk escapes 'withAltering', then it's going to be picking up a
168-
-- different state. 'Eval' lets us be deliberate about what escapes and what
177+
-- different state. 'strat' lets us be deliberate about what escapes and what
169178
-- doesn't. Namely the altered state is available precisely during the
170-
-- evaluation of `eval thing`.
171-
withAltering :: Eval b => Field a -> (Maybe a -> Maybe a) -> b -> Result b
172-
withAltering f g thing = unsafePerformIO $ withAlteringIO f g (PureEval thing)
179+
-- evaluation of `strat thing`.
180+
withAltering :: Field a -> (Maybe a -> Maybe a) -> Strat b -> b -> b
181+
withAltering f g strat thing = unsafePerformIO $ withAlteringIO f g strat thing
173182
{-# NOINLINE withAltering #-}
174183

175184
-- | TODO
176-
withAlteringIO :: EvalIO b => Field a -> (Maybe a -> Maybe a) -> b -> IO (ResultIO b)
177-
withAlteringIO f g thing = do
185+
withAlteringIO :: Field a -> (Maybe a -> Maybe a) -> Strat b -> b -> IO b
186+
withAlteringIO f g strat thing = do
178187
outer_vault <- myLocalState
179188
inner_vault <- evaluate $ alterVault g f outer_vault
180189
-- We make an `async` here, not because of concurrency: it's not going to be
@@ -198,7 +207,8 @@ withAlteringIO f g thing = do
198207
-- - Uses the GC to collect thread-local state: https://hackage.haskell.org/package/thread-utils-context
199208
me <- async $ do
200209
setLocalState inner_vault
201-
evalIO thing
210+
E <- evaluate $ strat thing
211+
return thing
202212
-- Note: this implementation relies of the fact that thread ids can't be
203213
-- reused, otherwise there may be races. I'm not sure that GHC guarantees
204214
-- this property.
@@ -209,34 +219,134 @@ withAlteringIO f g thing = do
209219
-- set to `a`.
210220
--
211221
-- See 'withAltering' for more explanations.
212-
withSetting :: Eval b => Field a -> a -> b -> Result b
222+
withSetting :: Field a -> a -> Strat b -> b -> b
213223
withSetting f a = withAltering f (\_ -> Just a)
214224

215225
-- | TODO
216-
withSettingIO :: EvalIO b => Field a -> a -> b -> IO (ResultIO b)
226+
withSettingIO :: Field a -> a -> Strat b -> b -> IO b
217227
withSettingIO f a = withAlteringIO f (\_ -> Just a)
218228

219229
-- | @'unsafeSpanBenign' before after thing@ runs the `before` action before
220-
-- evaluating `thing`, then runs the `after` action.
230+
-- evaluating `strat thing`, then runs the `after` action.
221231
--
222232
-- 'unsafeSpanBenign' is not typically used directly in programs, but used to
223233
-- write safe benign-effect-spanning functions.
224234
--
225235
-- To call 'unsafeSpanBenign' safely, make sure that the `before` and `after`
226236
-- actions are indeed benign.
227237
unsafeSpanBenign ::
228-
Eval a =>
229238
-- | Action to run before evaluation
230239
IO () ->
231240
-- | Action to run after evaluation
232241
IO () ->
242+
Strat a ->
233243
a ->
234-
Result a
235-
unsafeSpanBenign before after thing = unsafePerformIO $ do
236-
thunk <- bracket_ before after (evaluate $ eval thing)
237-
return $ extractEval thunk
244+
a
245+
unsafeSpanBenign before after strat thing = unsafePerformIO $ do
246+
E <- bracket_ before after (evaluate $ strat thing)
247+
return thing
238248
{-# NOINLINE unsafeSpanBenign #-}
239249

250+
---------------------------------------------------------------------------
251+
--
252+
-- Evaluate in a monad
253+
--
254+
---------------------------------------------------------------------------
255+
256+
-- | In non-IO monadic code (that is when monads are used as a way to organise
257+
-- pure code), naturally, we'll be wanting to use benign effect as well. How
258+
-- scopes and running monadic code interleave doesn't have a generic
259+
-- answer. This is because monadic code is fundamentally staged: first you build
260+
-- a monadic expression, then it is run. Benign effects, and in particular
261+
-- local state updates, must happen when the monad is run, not when the
262+
-- expression is built.
263+
--
264+
-- Just like there isn't a generic `run` function, since all monads interpret
265+
-- the monadic expression differently, each monad needs to explain how they
266+
-- implement 'withAltering' and 'unsafeSpanBenign'. This is what the (admittedly
267+
-- poorly named) 'EvalM' class lets monad do.
268+
class EvalM m where
269+
spliceEval :: (forall a. Strat a -> a -> a) -> Strat b -> m b -> m b
270+
271+
withAlteringM :: (EvalM m) => Field a -> (Maybe a -> Maybe a) -> Strat b -> m b -> m b
272+
withAlteringM f g = spliceEval (withAltering f g)
273+
274+
-- | Like 'withAlteringM', but the `strat` is `lazy`. This is most often the
275+
-- right choice in a monadic context.
276+
withAlteringM' :: (EvalM m) => Field a -> (Maybe a -> Maybe a) -> m b -> m b
277+
withAlteringM' f g = withAlteringM f g lazy
278+
279+
-- | Like 'withSetting', but in a monadic context. The `strat` is `lazy`. This
280+
-- is most often the right choice in a monadic context.
281+
withSettingM :: (EvalM m) => Field a -> a -> Strat b -> m b -> m b
282+
withSettingM f a = withAlteringM f (\_ -> Just a)
283+
284+
-- | Like 'withSettingM', but the `strat` is `lazy`. This is most often the
285+
-- right choice in a monadic context.
286+
withSettingM' :: (EvalM m) => Field a -> a -> m b -> m b
287+
withSettingM' f a = withSettingM f a lazy
288+
289+
unsafeSpanBenignM :: (EvalM m) => IO () -> IO () -> Strat a->m a -> m a
290+
unsafeSpanBenignM before after = spliceEval (unsafeSpanBenign before after)
291+
292+
-- | Like 'unsafeSpanBenignM', but the `strat` is `lazy`. This is most often the
293+
-- right choice in a monadic context.
294+
unsafeSpanBenignM' :: (EvalM m) => IO () -> IO () -> m a -> m a
295+
unsafeSpanBenignM' before after = unsafeSpanBenignM before after lazy
296+
297+
instance EvalM Identity where
298+
spliceEval :: forall b. (forall a. Strat a -> a -> a) -> Strat b -> Identity b -> Identity b
299+
spliceEval f = coerce $ f @b
300+
301+
-- | Doesn't evaluate the state. It would be possible to require `'Eval' s` so
302+
-- that the state can also be evaluated. Unclear what is the most natural.
303+
instance (EvalM m) => EvalM (StateT s m) where
304+
spliceEval f strat (StateT thing) = StateT $ \s -> spliceEval f tupStrat (thing s)
305+
where
306+
tupStrat (b, _) = strat b
307+
308+
instance (EvalM m) => EvalM (ReaderT e m) where
309+
spliceEval f strat (ReaderT thing) = ReaderT $ \e -> spliceEval f strat (thing e)
310+
311+
---------------------------------------------------------------------------
312+
--
313+
-- Evaluate in a monad
314+
--
315+
---------------------------------------------------------------------------
316+
317+
-- | Evaluation strategies. The idea is that evaluating with strategy `strat` is
318+
-- the same as evaluating `strat a` in whnf.
319+
--
320+
-- This is inspired by `Control.Seq.Strategy` from the
321+
-- [parallel](https://hackage.haskell.org/package/parallel) package. It's
322+
-- actually roughly the same type, but a little more modern. Making sure in
323+
-- particular that 'E', as a monoid, is strict, so that
324+
--
325+
-- > 'foldMap' :: Foldable t => Strat a -> Strat (t a)
326+
--
327+
-- Evaluates all the position in a container.
328+
type Strat a = a -> E
329+
330+
data E = E
331+
deriving stock (Eq, Ord, Enum, Bounded, Show, Read)
332+
333+
instance Semigroup E where
334+
E <> E = E
335+
336+
instance Monoid E where
337+
mempty = E
338+
339+
-- | Doesn't do any evaluation.
340+
lazy :: Strat a
341+
lazy = mempty
342+
343+
-- | Evaluates in whnf. Like 'seq'
344+
whnf :: Strat a
345+
whnf a = a `seq` E
346+
347+
nf :: (NFData a) => Strat a
348+
nf a = a `deepseq` E
349+
240350
class Eval a where
241351
data Thunk a
242352

@@ -351,43 +461,3 @@ newtype PureEval a = PureEval a
351461
instance Eval a => EvalIO (PureEval a) where
352462
type ResultIO (PureEval a) = Result a
353463
evalIO (PureEval a) = evalInIO a
354-
355-
---------------------------------------------------------------------------
356-
--
357-
-- Evaluate in a monad
358-
--
359-
---------------------------------------------------------------------------
360-
361-
-- | In non-IO monadic code (that is when monads are used as a way to organise
362-
-- pure code), naturally, we'll be wanting to use benign effect as well. How
363-
-- scopes and running monadic code interleave doesn't have a generic
364-
-- answer. This is because monadic code is fundamentally staged: first you build
365-
-- a monadic expression, then it is run. Benign effects, and in particular
366-
-- local state updates, must happen when the monad is run, not when the
367-
-- expression is built.
368-
--
369-
-- Just like there isn't a generic `run` function, since all monads interpret
370-
-- the monadic expression differently, each monad needs to explain how they
371-
-- implement 'withAltering' and 'unsafeSpanBenign'. This is what the (admittedly
372-
-- poorly named) 'EvalM' class lets monad do.
373-
class EvalM m where
374-
spliceEval :: Eval b => (forall a. Eval a => (a -> Result a)) -> m b -> m (Result b)
375-
376-
withAlteringM :: (EvalM m, Eval b) => Field a -> (Maybe a -> Maybe a) -> m b -> m (Result b)
377-
withAlteringM f g = spliceEval (withAltering f g)
378-
379-
withSettingM :: (EvalM m, Eval b) => Field a -> a -> m b -> m (Result b)
380-
withSettingM f a = withAlteringM f (\_ -> Just a)
381-
382-
unsafeSpanBenignM :: (EvalM m, Eval a) => IO () -> IO () -> m a -> m (Result a)
383-
unsafeSpanBenignM before after = spliceEval (unsafeSpanBenign before after)
384-
385-
instance EvalM Identity where
386-
spliceEval :: forall b. Eval b => (forall a. Eval a => a -> Result a) -> Identity b -> Identity (Result b)
387-
spliceEval f = coerce $ f @b
388-
389-
instance (EvalM m, Eval s, Result s ~ s) => EvalM (StateT s m) where
390-
spliceEval f (StateT thing) = StateT $ \s -> spliceEval f (thing s)
391-
392-
instance (EvalM m) => EvalM (ReaderT e m) where
393-
spliceEval f (ReaderT thing) = ReaderT $ \e -> spliceEval f (thing e)

0 commit comments

Comments
 (0)