benign-0.1.0: A library for benign effects
Safe HaskellNone
LanguageGHC2021

Benign

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. withAlteringM and withAlteringM'). The primed version doesn't take an evaluation strategy as an argument (the evaluation strategy is effectively lazy). Because quite often, in monadic code, you want to span over just the monadic computation, not necessarily the evaluation of the result.
Synopsis

Documentation

unsafeSpanBenign Source #

Arguments

:: IO ()

Action to run before evaluation

-> IO ()

Action to run after evaluation

-> Strat a 
-> a 
-> a 

unsafeSpanBenign before after thing runs the before 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.

data Field (a :: k) Source #

A (typed) key which lets you retrieve data in the lexical state.

Instances

Instances details
Eq (Field a) Source # 
Instance details

Defined in Benign

Methods

(==) :: Field a -> Field a -> Bool #

(/=) :: Field a -> Field a -> Bool #

Ord (Field a) Source # 
Instance details

Defined in Benign

Methods

compare :: Field a -> Field a -> Ordering #

(<) :: Field a -> Field a -> Bool #

(<=) :: Field a -> Field a -> Bool #

(>) :: Field a -> Field a -> Bool #

(>=) :: Field a -> Field a -> Bool #

max :: Field a -> Field a -> Field a #

min :: Field a -> Field a -> Field a #

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 #

withAltering f g strat thing evaluates thing 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 #

withSetting f a thing evaluates thing 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 #

lookupLexicalState f returns Just 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 #

lookupLexicalState f returns a 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 #

lookupLexicalState d f returns a 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 #

Instances

Instances details
EvalM Identity Source # 
Instance details

Defined in Benign

Methods

spliceEval :: (forall a. Strat a -> a -> a) -> Strat b -> Identity b -> Identity b Source #

EvalM m => EvalM (ReaderT e m) Source # 
Instance details

Defined in Benign

Methods

spliceEval :: (forall a. Strat a -> a -> a) -> Strat b -> ReaderT e m b -> ReaderT e m b Source #

EvalM m => EvalM (StateT s m) Source #

Doesn't evaluate the state. It would be possible to require `Eval s` so that the state can also be evaluated. Unclear what is the most natural.

Instance details

Defined in Benign

Methods

spliceEval :: (forall a. Strat a -> a -> a) -> Strat b -> StateT s m b -> StateT s 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 withAltering f g strat thing, except that thing 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.

data E Source #

A unit type with a strict monoid instance.

Constructors

E 

Instances

Instances details
Monoid E Source # 
Instance details

Defined in Benign

Methods

mempty :: E #

mappend :: E -> E -> E #

mconcat :: [E] -> E #

Semigroup E Source # 
Instance details

Defined in Benign

Methods

(<>) :: E -> E -> E #

sconcat :: NonEmpty E -> E #

stimes :: Integral b => b -> E -> E #

Bounded E Source # 
Instance details

Defined in Benign

Methods

minBound :: E #

maxBound :: E #

Enum E Source # 
Instance details

Defined in Benign

Methods

succ :: E -> E #

pred :: E -> E #

toEnum :: Int -> E #

fromEnum :: E -> Int #

enumFrom :: E -> [E] #

enumFromThen :: E -> E -> [E] #

enumFromTo :: E -> E -> [E] #

enumFromThenTo :: E -> E -> E -> [E] #

Read E Source # 
Instance details

Defined in Benign

Show E Source # 
Instance details

Defined in Benign

Methods

showsPrec :: Int -> E -> ShowS #

show :: E -> String #

showList :: [E] -> ShowS #

Eq E Source # 
Instance details

Defined in Benign

Methods

(==) :: E -> E -> Bool #

(/=) :: E -> E -> Bool #

Ord E Source # 
Instance details

Defined in Benign

Methods

compare :: E -> E -> Ordering #

(<) :: E -> E -> Bool #

(<=) :: E -> E -> Bool #

(>) :: E -> E -> Bool #

(>=) :: E -> E -> Bool #

max :: E -> E -> E #

min :: E -> E -> E #

lazy :: Strat a Source #

Doesn't do any evaluation.

whnf :: Strat a Source #

Evaluates in whnf. Like seq

nf :: NFData a => Strat a Source #

class Eval a where Source #

Methods

eval :: Strat a Source #

A canonical strategy for type a.

Instances

Instances details
Eval Int32 Source # 
Instance details

Defined in Benign

Methods

eval :: Strat Int32 Source #

Eval Int64 Source # 
Instance details

Defined in Benign

Methods

eval :: Strat Int64 Source #

Eval Word32 Source # 
Instance details

Defined in Benign

Eval Word64 Source # 
Instance details

Defined in Benign

Eval Ordering Source # 
Instance details

Defined in Benign

Eval () Source # 
Instance details

Defined in Benign

Methods

eval :: Strat () Source #

Eval Bool Source # 
Instance details

Defined in Benign

Methods

eval :: Strat Bool Source #

Eval Char Source # 
Instance details

Defined in Benign

Methods

eval :: Strat Char Source #

Eval Double Source # 
Instance details

Defined in Benign

Eval Float Source # 
Instance details

Defined in Benign

Methods

eval :: Strat Float Source #

Eval Int Source # 
Instance details

Defined in Benign

Methods

eval :: Strat Int Source #

Eval Word Source # 
Instance details

Defined in Benign

Methods

eval :: Strat Word Source #

Eval a => Eval (Maybe a) Source # 
Instance details

Defined in Benign

Methods

eval :: Strat (Maybe a) Source #

Eval a => Eval [a] Source # 
Instance details

Defined in Benign

Methods

eval :: Strat [a] Source #

(Eval a, Eval b) => Eval (Either a b) Source # 
Instance details

Defined in Benign

Methods

eval :: Strat (Either a b) Source #

(Eval a, Eval b) => Eval (a, b) Source # 
Instance details

Defined in Benign

Methods

eval :: Strat (a, b) Source #

(Eval a, Eval b, Eval c) => Eval (a, b, c) Source # 
Instance details

Defined in Benign

Methods

eval :: Strat (a, b, c) Source #

(Eval a, Eval b, Eval c, Eval d) => Eval (a, b, c, d) Source # 
Instance details

Defined in Benign

Methods

eval :: Strat (a, b, c, d) Source #