@@ -43,6 +43,10 @@ module Benign
4343 NF (.. ),
4444 EvalIO (.. ),
4545 PureEval (.. ),
46+
47+ -- * Monads
48+ EvalM (.. ),
49+ withSettingM ,
4650 )
4751where
4852
@@ -51,6 +55,10 @@ import Control.Concurrent.Async (async)
5155import Control.Concurrent.Async qualified as Async
5256import Control.DeepSeq
5357import Control.Exception (bracket_ , evaluate , finally )
58+ import Control.Monad.Trans.Reader
59+ import Control.Monad.Trans.State.Lazy
60+ import Data.Coerce
61+ import Data.Functor.Identity
5462import Data.Int
5563import Data.Map.Strict (Map )
5664import Data.Map.Strict qualified as Map
@@ -239,6 +247,12 @@ class Eval a where
239247
240248 extractEval :: Thunk a -> Result a
241249
250+ instance (Eval a , Eval b ) => Eval (a , b ) where
251+ data Thunk (a , b ) = PairThunk (Thunk a ) (Thunk b )
252+ type Result (a , b ) = (Result a , Result b )
253+ eval (a , b ) = PairThunk (eval a ) (eval b )
254+ extractEval (PairThunk a b ) = (extractEval a , extractEval b )
255+
242256-- | Evaluation strategy: evaluates `a` by simply calling `seq` on it.
243257newtype Seq a = Seq a
244258 deriving anyclass (EvalIO )
@@ -335,3 +349,45 @@ newtype PureEval a = PureEval a
335349instance Eval a => EvalIO (PureEval a ) where
336350 type ResultIO (PureEval a ) = Result a
337351 evalIO (PureEval a) = evalInIO a
352+
353+ ---------------------------------------------------------------------------
354+ --
355+ -- Evaluate in a monad
356+ --
357+ ---------------------------------------------------------------------------
358+
359+ -- | In non-IO monadic code (that is when monads are used as a way to organise
360+ -- pure code), naturally, we'll be wanting to use benign effect as well. How
361+ -- scopes and running monadic code interleave doesn't have a generic
362+ -- answer. This is because monadic code is fundamentally staged: first you build
363+ -- a monadic expression, then it is run. Benign effects, and in particular
364+ -- local state updates, must happen when the monad is run, not when the
365+ -- expression is built.
366+ --
367+ -- Just like there isn't a generic `run` function, since all monads interpret
368+ -- the monadic expression differently, each monad needs to explain how they
369+ -- implement 'withAltering' and 'unsafeSpanBenign'. This is what the (admittedly
370+ -- poorly named) 'EvalM' class lets monad do.
371+ class EvalM m where
372+ withAlteringM :: Eval b => Field a -> (Maybe a -> Maybe a ) -> m b -> m (Result b )
373+ unsafeSpanBenignM :: Eval a => IO () -> IO () -> m a -> m (Result a )
374+
375+ withSettingM :: (EvalM m , Eval b ) => Field a -> a -> m b -> m (Result b )
376+ withSettingM f a = withAlteringM f (\ _ -> Just a)
377+
378+ instance EvalM Identity where
379+ withAlteringM :: forall b a . Eval b => Field a -> (Maybe a -> Maybe a ) -> Identity b -> Identity (Result b )
380+ withAlteringM = coerce $ withAltering @ b @ a
381+ unsafeSpanBenignM :: forall a . Eval a => IO () -> IO () -> Identity a -> Identity (Result a )
382+ unsafeSpanBenignM = coerce $ unsafeSpanBenign @ a
383+
384+ instance (EvalM m , Eval s , Result s ~ s ) => EvalM (StateT s m ) where
385+ withAlteringM f g (StateT thing) = StateT $ \ s -> withAlteringM f g (thing s)
386+
387+ unsafeSpanBenignM :: (EvalM m , Eval s , Result s ~ s , Eval a ) => IO () -> IO () -> StateT s m a -> StateT s m (Result a )
388+ unsafeSpanBenignM before after (StateT thing) = StateT $ \ s -> unsafeSpanBenignM before after (thing s)
389+
390+ instance (EvalM m ) => EvalM (ReaderT e m ) where
391+ withAlteringM f g (ReaderT thing) = ReaderT $ \ e -> withAlteringM f g (thing e)
392+
393+ unsafeSpanBenignM before after (ReaderT thing) = ReaderT $ \ e -> unsafeSpanBenignM before after (thing e)
0 commit comments