@@ -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 )
5362where
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
213223withSetting 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
217227withSettingIO 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.
227237unsafeSpanBenign ::
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+
240350class Eval a where
241351 data Thunk a
242352
@@ -351,43 +461,3 @@ newtype PureEval a = PureEval a
351461instance 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