Copyright | (c) Samuel Schlesinger 2020 |
---|---|

License | MIT |

Maintainer | sgschlesinger@gmail.com |

Stability | experimental |

Portability | POSIX, Windows |

Safe Haskell | Safe |

Language | Haskell2010 |

This library exposes a minimal interface for incremental computation, where we decompose our computation into the explicit computational dependencies and only recompute things when their dependencies have been recomputed. Here is a basic example:

main = do (a, b, c, d) <- atomically $ do a <- incremental 0 b <- incremental 1 c <- map (+ 1) b d <- combine (+) c b pure (a, b, c, d) guard =<< (atomically $ do (== 3) $ observe d) guard =<< (atomically $ do set a 1 set b 0 (== 1) $ observe d)

Here, we see that we can set some variables and notice their changes, all done
in `STM`

`atomically`

blocks which allows us to combine these computations
transactionally with other logic we've written in `STM`

. What happens if we try
to write to `c`

? Well, we get a type error! The only `Incremental`

computations
which we can set new values for are the leaves of the computation, constructed
with `incremental`

. This is this way because we don't want to mutate `Incremental`

s
downstream of dependencies, because then we cannot know that those computations
remain consistent. Most operations are polymorphic over `Mutable`

and `Immutable`

`Incremental`

s, but `map`

, `combine`

, and `choose`

all produce an

,
whereas `Incremental`

`Immutable`

`incremental`

produces a

.`Incremental`

`Mutable`

## Synopsis

- data Mutability
- data Incremental (mutability :: Mutability) a
- incremental :: a -> STM (Incremental Mutable a)
- observe :: Incremental m a -> STM a
- set :: Incremental Mutable a -> a -> STM ()
- setEq :: Eq a => Incremental Mutable a -> a -> STM ()
- map :: (a -> b) -> Incremental m a -> STM (Incremental Immutable b)
- mapEq :: Eq b => (a -> b) -> Incremental m a -> STM (Incremental Immutable b)
- combine :: (a -> b -> c) -> Incremental m a -> Incremental m' b -> STM (Incremental Immutable c)
- combineEq :: Eq c => (a -> b -> c) -> Incremental m a -> Incremental m' b -> STM (Incremental Immutable c)
- choose :: Incremental m' a -> (a -> Incremental m b) -> STM (Incremental Immutable b)
- chooseEq :: Eq b => Incremental m' a -> (a -> Incremental m b) -> STM (Incremental Immutable b)
- immutable :: Incremental Mutable b -> Incremental Immutable b
- onUpdate :: Incremental m b -> (b -> STM ()) -> STM ()
- history :: Incremental m b -> STM (STM [b])

# Documentation

data Mutability Source #

A data kind intended to help us expose a safe interface, only allowing us to modify leaf nodes of computational graphs as to avoid inconsistent states.

data Incremental (mutability :: Mutability) a Source #

An incremental computation, only updated when one of its dependencies is.

incremental :: a -> STM (Incremental Mutable a) Source #

Construct a trivial, mutable incremental computation. This is mutable precisely because it is trivial, as modifications to anything else cannot cause it to be modified. Thus, we can change it willy nilly without fear that we've invalidated someone else's changes to it.

observe :: Incremental m a -> STM a Source #

Observes the present value of any incremental computation.

set :: Incremental Mutable a -> a -> STM () Source #

Sets the value of a mutable incremental computation.

setEq :: Eq a => Incremental Mutable a -> a -> STM () Source #

Sets the value of a mutable incremental computation, with the added
optimization that if the value is equal to the old one, this does not
update the dependents of this `Incremental`

value.

map :: (a -> b) -> Incremental m a -> STM (Incremental Immutable b) Source #

Create an incrementally mapped computation, producing an immutable incremental computation that will always contain the function mapped over the value inside of the original computation.

mapEq :: Eq b => (a -> b) -> Incremental m a -> STM (Incremental Immutable b) Source #

Create an incrementally mapped computation, producing an immutable incremental computation that will always contain the function mapped over the value inside of the original computation.

combine :: (a -> b -> c) -> Incremental m a -> Incremental m' b -> STM (Incremental Immutable c) Source #

Combines the results of two incremental computation into a third, producing an immutable incremental computation that will always contain the function mapped over both of the values inside of the respective incremental computations.

combineEq :: Eq c => (a -> b -> c) -> Incremental m a -> Incremental m' b -> STM (Incremental Immutable c) Source #

Like `combine`

, but with the added optimization that if the value
computed is equal to the old one, it will not propagate the update
through any further.

choose :: Incremental m' a -> (a -> Incremental m b) -> STM (Incremental Immutable b) Source #

Chooses an incremental computation depending on the value inside of another
one. When using `map`

and `combine`

, you are constructing
a static dependency graph, whereas when you use this function you are
making it dynamic, the linkages depending on the actual contents of the
incremental computation nodes.

chooseEq :: Eq b => Incremental m' a -> (a -> Incremental m b) -> STM (Incremental Immutable b) Source #

immutable :: Incremental Mutable b -> Incremental Immutable b Source #

Sometimes, we need to consider an

in
a setting alongside `Incremental`

`Mutable`

values, unifying their
type. One example where this is common is in `Incremental`

`Immutable`

`choose`

:

... x <- incremental True y <- map not x z <- choose y (bool y x) ...

This code will not compile, because `y`

and `x`

have different types, but this will:

... x <- incremental True y <- map not x z <- choose y (bool y (immutable x)) ...

onUpdate :: Incremental m b -> (b -> STM ()) -> STM () Source #

Add monitoring hooks which can do arbitrary actions in `STM`

with the
changed value whenever this `Incremental`

is updated. One useful example
of this is used for testing this module, recording the `history`

of an
`Incremental`

computation:

history :: Incremental m b -> STM (STM [b]) history i = do x <- observe i h <- newTVar [x] onUpdate i b -> do bs <- readTVar h writeTVar h (b : bs) pure (readTVar h)

history :: Incremental m b -> STM (STM [b]) Source #

Instruments the given `Incremental`

with functionality to save the
history of its values. This is useful for testing.