Skip to content

Commit dae85e2

Browse files
authored
Merge pull request #23 from aspiwack/monads
An attempt at making benign effects available in monadic code
2 parents c35288f + 62abd5d commit dae85e2

File tree

3 files changed

+59
-0
lines changed

3 files changed

+59
-0
lines changed

benign.cabal

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ library
3535
, deepseq
3636
, stm
3737
, strict-wrapper
38+
, transformers
3839
default-language: Haskell2010
3940

4041
executable simple-print
@@ -50,4 +51,5 @@ executable simple-print
5051
, deepseq
5152
, stm
5253
, strict-wrapper
54+
, transformers
5355
default-language: Haskell2010

package.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ dependencies:
1414
- deepseq
1515
- stm
1616
- strict-wrapper
17+
- transformers
1718

1819
ghc-options: -Wall -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns -Wnoncanonical-monad-instances -Wredundant-constraints
1920

src/Benign.hs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ module Benign
4343
NF (..),
4444
EvalIO (..),
4545
PureEval (..),
46+
47+
-- * Monads
48+
EvalM (..),
49+
withSettingM,
4650
)
4751
where
4852

@@ -51,6 +55,10 @@ import Control.Concurrent.Async (async)
5155
import Control.Concurrent.Async qualified as Async
5256
import Control.DeepSeq
5357
import 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
5462
import Data.Int
5563
import Data.Map.Strict (Map)
5664
import 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.
243257
newtype Seq a = Seq a
244258
deriving anyclass (EvalIO)
@@ -335,3 +349,45 @@ newtype PureEval a = PureEval a
335349
instance 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

Comments
 (0)