| Safe Haskell | None |
|---|---|
| Language | GHC2021 |
Benign
Contents
Description
Benign effects are actions which are nominally effects but really doesn't affect the semantics of your programs such as logging and tracing. Such effects can be run in pure code.
Laziness doesn't always make it easy to add benign effects. Running a pure lazy expression has a beginning (when the thunk is being evaluated) but no real end. For tracing, for instance, you need to mark the end of evaluation.
Evaluation strategies
This library uses the evaluation strategies (Strat) to add a (programmable)
end to expressions. If you have a function span "begin" "end" to log
strings before and after an expression, you could write
span "begin" "end" whnf u
To mean that "end" is logged after evaluating u in weak
head normal form. whnf is an evaluation strategies. Another one is nf
span "begin" "end" nf u
To mean that "end" is logged after evaluating u in normal form, this
time. Most functions in this module take an evaluation strategy as an
argument. You can evaluate as much or as little as you want.
Conventions
- A convention in this module is that functions which act on monadic code
have a primed version (e.g.
withAlteringMandwithAlteringM'). The primed version doesn't take an evaluation strategy as an argument (the evaluation strategy is effectivelylazy). Because quite often, in monadic code, you want to span over just the monadic computation, not necessarily the evaluation of the result.
Synopsis
- unsafeSpanBenign :: IO () -> IO () -> Strat a -> a -> a
- data Field (a :: k)
- newField :: forall {k} (a :: k). IO (Field a)
- withAltering :: Field a -> (Maybe a -> Maybe a) -> Strat b -> b -> b
- withSetting :: Field a -> a -> Strat b -> b -> b
- withAlteringIO :: Field a -> (Maybe a -> Maybe a) -> Strat b -> IO b -> IO b
- withAlteringIO' :: Field a -> (Maybe a -> Maybe a) -> IO b -> IO b
- withSettingIO :: Field a -> a -> Strat b -> IO b -> IO b
- withSettingIO' :: Field a -> a -> IO b -> IO b
- lookupLexicalState :: Field a -> IO (Maybe a)
- lookupLexicalState' :: HasCallStack => Field a -> IO a
- lookupLexicalStateWithDefault :: a -> Field a -> IO a
- class EvalM (m :: Type -> Type) where
- spliceEval :: (forall a. Strat a -> a -> a) -> Strat b -> m b -> m b
- withAlteringM :: EvalM m => Field a -> (Maybe a -> Maybe a) -> Strat b -> m b -> m b
- withAlteringM' :: EvalM m => Field a -> (Maybe a -> Maybe a) -> m b -> m b
- withSettingM :: EvalM m => Field a -> a -> Strat b -> m b -> m b
- withSettingM' :: EvalM m => Field a -> a -> m b -> m b
- unsafeSpanBenignM :: EvalM m => IO () -> IO () -> Strat a -> m a -> m a
- unsafeSpanBenignM' :: EvalM m => IO () -> IO () -> m a -> m a
- type Strat a = a -> E
- data E = E
- lazy :: Strat a
- whnf :: Strat a
- nf :: NFData a => Strat a
- class Eval a where
Documentation
Arguments
| :: IO () | Action to run before evaluation |
| -> IO () | Action to run after evaluation |
| -> Strat a | |
| -> a | |
| -> a |
runs the unsafeSpanBenign before after thingbefore action before
evaluating `strat thing`, then runs the after action.
unsafeSpanBenign is not typically used directly in programs, but used to
write safe benign-effect-spanning functions. It's a typical way of declaring
effects as benign.
To call unsafeSpanBenign safely, make sure that the before and after
actions are indeed benign.
An alternative to unsafeSpanBenign is to simply call unsafePerformIO
directly over a bracket. See the source code for unsafeSpanBenign.
Lexical state
The Benign library provides a lexical state facility. That is
a piece of state which is modified within a scope (like local for the
Reader monad), rather than for the rest of the program (like put for the
State monad).
Why do we need this? If we are to do some logging in pure code, we still need to know where to log too. That is we need configuration. It is out of the question to modify all the pure code to take the configuration as arguments (either explicit, implicit, or with a monad): this would force us to pass arguments down all the functions that call functions that ultimately call a logging function. It would be majorly inconvenient, but mostly it goes against the fact that the code is pure. If we go this way, we may as well write all the code in monadic style. It is an assumption of this library that we don't want to do this (and it seems to be supported by experience that most Haskell programmers don't want to write most of their code in a monad).
So we need a way to pass some state to pure code, to be used in benign
effects (see lookupLexicalState and unsafeSpanBenign). This is what
withAltering (and friends) achieves.
Another example of state which makes it especially obvious that we want a lexical state rather that a global state: some logging frameworks, such as Katip, let you add some context to logging strings. This context, of course, is lexical (you don't modify the logging context in another thread to reflect what's going on in your thread).
At any rate, since functions like withAltering are exposed as a pure
function, it wouldn't make much sense to modify a global state. The
modification could happen at any time and in any order. The result would be
quite ill-defined.
A (typed) key which lets you retrieve data in the lexical state.
newField :: forall {k} (a :: k). IO (Field a) Source #
Create a new field.
Fields are typically not exposed directly but only used internally to a module to make sure that other module don't create inconsistent contexts for you.
withAltering :: Field a -> (Maybe a -> Maybe a) -> Strat b -> b -> b Source #
evaluates withAltering f g strat thingthing with the lexical state's
field f set by g in the style of alter.
The reason why we need the strat argument is that, as a direct consequence
of the design, the lexical state is passed dynamically to thing. That is,
the state that is seen by a piece of code depends on where it's executed: if
a lazy thunk escapes withAltering, then it's going to be picking up a
different state. strat lets us be deliberate about what escapes and what
doesn't. Namely the altered state is available precisely during the
evaluation of strat thing.
withSetting :: Field a -> a -> Strat b -> b -> b Source #
evaluates withSetting f a thingthing with the local state's field f
set to a.
See withAltering for more explanations.
withAlteringIO :: Field a -> (Maybe a -> Maybe a) -> Strat b -> IO b -> IO b Source #
This lets you modify the lexical state in an IO monad scope. The lexical
state is still shared. See also withAltering.
withAlteringIO' :: Field a -> (Maybe a -> Maybe a) -> IO b -> IO b Source #
Like withAlteringIO, but the strat is lazy.
withSettingIO :: Field a -> a -> Strat b -> IO b -> IO b Source #
Sets the lexical state in an IO context. See also withSetting and
withAlteringIO.
withSettingIO' :: Field a -> a -> IO b -> IO b Source #
Like withSettingIO, but the strat is lazy.
lookupLexicalState :: Field a -> IO (Maybe a) Source #
returns lookupLexicalState fJust a if a is the (lexical) value of
field f in the lexical state. It returns Nothing is the field is unset.
lookupLexicalState' :: HasCallStack => Field a -> IO a Source #
returns lookupLexicalState fa if a is the (lexical) value of
field f in the lexical state. It throws an error if the field is unset.
lookupLexicalStateWithDefault :: a -> Field a -> IO a Source #
returns lookupLexicalState d fa if a is the (lexical) value of
field f in the lexical state. It returns d if the field is unset.
Monads
class EvalM (m :: Type -> Type) where Source #
In non-IO monadic code (that is when monads are used as a way to organise pure code), naturally, we'll be wanting to use benign effect as well. How scopes and running monadic code interleave doesn't have a generic answer. This is because monadic code is fundamentally staged: first you build a monadic expression, then it is run. Benign effects, and in particular local state updates, must happen when the monad is run, not when the expression is built.
Just like there isn't a generic run function, since all monads interpret
the monadic expression differently, each monad needs to explain how they
implement withAltering and unsafeSpanBenign. This is what the (admittedly
poorly named) EvalM class lets monad do.
Methods
spliceEval :: (forall a. Strat a -> a -> a) -> Strat b -> m b -> m b Source #
withAlteringM :: EvalM m => Field a -> (Maybe a -> Maybe a) -> Strat b -> m b -> m b Source #
'withAlteringM f g strat thing works like , except that withAltering f g strat
thingthing is a monadic action. The strat evaluates the
return value. 'withAlteringM f g strat thing spans over the evaluation. See
also EvalM.
withAlteringM' :: EvalM m => Field a -> (Maybe a -> Maybe a) -> m b -> m b Source #
Like withAlteringM, but the strat is lazy.
withSettingM :: EvalM m => Field a -> a -> Strat b -> m b -> m b Source #
Like withSetting, but in a monadic context. See also withAlteringM.
withSettingM' :: EvalM m => Field a -> a -> m b -> m b Source #
Like withSettingM, but the strat is lazy.
unsafeSpanBenignM :: EvalM m => IO () -> IO () -> Strat a -> m a -> m a Source #
Like unsafeSpanBenign, but in a monadic context. See also
withAlteringM.
unsafeSpanBenignM' :: EvalM m => IO () -> IO () -> m a -> m a Source #
Like unsafeSpanBenignM, but the strat is lazy.
Strategies
type Strat a = a -> E Source #
Evaluation strategies. The idea is that evaluating with strategy strat is
the same as evaluating `strat a` in whnf.
This is inspired by Strategy from the
parallel package. It's
actually roughly the same type, but a little more modern. Making sure in
particular that E, as a monoid, is strict, so that
foldMap :: Foldable t => Strat a -> Strat (t a)
Evaluates all the positions in a container.
A unit type with a strict monoid instance.
Constructors
| E |
Instances
| Eval Int32 Source # | |
| Eval Int64 Source # | |
| Eval Word32 Source # | |
| Eval Word64 Source # | |
| Eval Ordering Source # | |
| Eval () Source # | |
| Eval Bool Source # | |
| Eval Char Source # | |
| Eval Double Source # | |
| Eval Float Source # | |
| Eval Int Source # | |
| Eval Word Source # | |
| Eval a => Eval (Maybe a) Source # | |
| Eval a => Eval [a] Source # | |
| (Eval a, Eval b) => Eval (Either a b) Source # | |
| (Eval a, Eval b) => Eval (a, b) Source # | |
| (Eval a, Eval b, Eval c) => Eval (a, b, c) Source # | |
| (Eval a, Eval b, Eval c, Eval d) => Eval (a, b, c, d) Source # | |