auto-0.3.0.0: Denotative, locally stateful programming DSL & platform

Copyright(c) Justin Le 2015
LicenseMIT
Maintainerjustin@jle.im
Stabilityunstable
Portabilityportable
Safe HaskellNone
LanguageHaskell2010

Control.Auto.Effects

Contents

Description

This module exports the preferred ways of interacting with the underlying Monad of the Auto type, including accessing, executing, and manipulating such effects.

Synopsis

Running effects

Continually

arrM Source

Arguments

:: (a -> m b)

monadic function

-> Auto m a b 

Applies the given "monadic function" (function returning a monadic action) to every incoming item; the result is the result of executing the action returned.

Note that this essentially lifts a "Kleisli arrow"; it's like arr, but for "monadic functions" instead of normal functions:

arr  :: (a -> b)   -> Auto m a b
arrM :: (a -> m b) -> Auto m a b
arrM f . arrM g == arrM (f <=< g)

One neat trick you can do is that you can "tag on effects" to a normal Auto by using *> from Control.Applicative. For example:

>>> let a = arrM print *> sumFrom 0
>>> ys <- streamAuto a [1..5]
1                -- IO output
2
3
4
5
>>> ys
[1,3,6,10,15]    -- the result

Here, a behaves "just like" sumFrom 0...except, when you step it, it prints out to stdout as a side-effect. We just gave automatic stdout logging behavior!

effect Source

Arguments

:: m b

monadic action to contually execute.

-> Auto m a b 

To get every output, executes the monadic action and returns the result as the output. Always ignores input.

This is basically like an "effectful" pure:

pure   :: b   -> Auto m a b
effect :: m b -> Auto m a b

The output of pure is always the same, and the output of effect is always the result of the same monadic action. Both ignore their inputs.

Fun times when the underling Monad is, for instance, Reader.

>>> let a = effect ask    :: Auto (Reader b) a b
>>> let r = evalAuto a () :: Reader b b
>>> runReader r "hello"
"hello"
>>> runReader r 100
100

If your underling monad has effects (IO, State, Maybe, Writer, etc.), then it might be fun to take advantage of *> from Control.Applicative to "tack on" an effect to a normal Auto:

>>> let a = effect (modify (+1)) *> sumFrom 0 :: Auto (State Int) Int Int
>>> let st = streamAuto a [1..10]
>>> let (ys, s') = runState st 0
>>> ys
[1,3,6,10,15,21,28,36,45,55]
>>> s'
10

Out Auto a behaves exactly like sumFrom 0, except at each step, it also increments the underlying/global state by one. It is sumFrom 0 with an "attached effect".

From inputs

effects :: Monad m => Auto m (m a) a Source

The input stream is a stream of monadic actions, and the output stream is the result of their executions, through executing them.

On Blips

arrMB :: Monad m => (a -> m b) -> Auto m (Blip a) (Blip b) Source

Maps one blip stream to another; replaces every emitted value with the result of the monadic function, executing it to get the result.

effectB :: Monad m => m b -> Auto m (Blip a) (Blip b) Source

Maps one blip stream to another; replaces every emitted value with the result of a fixed monadic action, run every time an emitted value is received.

execB :: Monad m => m b -> Auto m (Blip a) (Blip a) Source

Outputs the identical blip stream that is received; however, every time it sees an emitted value, executes the given monadic action on the side.

One-time effects

cache Source

Arguments

:: (Serialize b, Monad m) 
=> m b

monadic action to execute and use the result of

-> Auto m a b 

The very first output executes a monadic action and uses the result as the output, ignoring all input. From then on, it persistently outputs that first result.

Like execOnce, except outputs the result of the action instead of ignoring it.

Useful for loading resources in IO on the "first step", like a word list:

dictionary :: Auto IO a [String]
dictionary = cache (lines $ readFile "wordlist.txt")

execOnce Source

Arguments

:: Monad m 
=> m b

monadic action to execute; result discared

-> Auto m a () 

Always outputs '()', but when asked for the first output, executes the given monadic action.

Pretty much like cache, but always outputs '()'.

cache_ Source

Arguments

:: Monad m 
=> m b

monadic action to execute and use the result of

-> Auto m a b 

The non-resumable/non-serializable version of cache. Every time the Auto is deserialized/reloaded, it re-executes the action to retrieve the result again.

Useful in cases where you want to "re-load" an expensive resource on every startup, instead of saving it to in the save states.

dictionary :: Auto IO a [String]
dictionary = cache_ (lines $ readFile "dictionary.txt")

execOnce_ Source

Arguments

:: Monad m 
=> m b

monadic action to execute; result discared

-> Auto m a () 

The non-resumable/non-serializable version of execOnce. Every time the Auto is deserialized/reloaded, the action is re-executed again.

Hoists

hoistA Source

Arguments

:: (Monad m, Monad m') 
=> (forall c. m c -> m' c)

monad morphism; the natural transformation

-> Auto m a b 
-> Auto m' a b 

Swaps out the underlying Monad of an Auto using the given monad morphism "transforming function", a natural transformation.

Basically, given a function to "swap out" any m a with an m' a, it swaps out the underlying monad of the Auto.

This forms a functor, so you rest assured in things like this:

hoistA id == id
hoistA f a1 . hoistA f a2 == hoistA f (a1 . a2)

generalizeA :: Monad m => Auto' a b -> Auto m a b Source

Generalizes an Auto' a b to an Auto m a b' for any Monad m, using hoist.

You generally should be able to avoid using this if you never directly write any Auto's and always write 'Auto m' parameterized over all Monads, but...in case you import one from a library or something, you can use this.

Specific underlying monads

Autos can be run in the context of an underlying monad; this means that, instead of just being a straight-up [a] -> [b], pairing up each a with a b, you can actually attach a "context" to the b-making process, in order to enrich your streaming logic with things like a global read-only environment, a global sink, or global mutable state. The main benefit is that these things all compose like any other Auto...they compose with ., you can use Applicative, Arrow, etc., and they'll combine properly.

For the most part, a good general philosophy is to only have a "small part" of your program over a monad. You might have a small region of your program that would benefit from having a global environment, a small region of your program that would benefit from having a sink, or a small program that would benefit from global mutable state. Exercise good style and write maintainable code by limiting the effectful parts to the bare minimum essential, then using runReaderA, sealReader, runWriterA, sealStateA, etc. to "close off" or seal the effects, and use the Auto like a normal one without effects.

In this section are combinators for working with specific underlying monads...and a little description on how each might be useful. Remember to use them wisely! Adding any underlying monad causes the complexity of reasoning with your code to go up (depending on which monad), so make sure that you get a real gain before using these!

ReaderT

Reader, or ReaderT is probably one of the most useful underlying monads to work with. Basically, instead of [a] -> [b], you have [a] -> r -> [b]. Generate b's, but with an r parameter you can always access. In practice, you can use Reader to hide a lot of boilerplate threading, add an extra "side input" channel, or compose Autos with a static guarantee that all Autos composed will use the same r environment.

Using effect, you have access to the environment:

effect ask :: MonadReader r m => Auto m a r

Which is an Auto where the only thing it does is continually output the environment r. You can throw this into any proc block over Reader, and you have a way to bring your environment "into scope":

env <- effect ask -< ()

For a use case example, you might have:

foo :: Auto m (Int, Database) Bool
bar :: Auto m (Bool, Database) Int
baz :: Auto m (Bool, Database) String

Where every Auto use a Database parameter to do their job...and it only makes sense when all of them are composed under the same Database. You can use normal proc notation:

full :: Auto m (Int, Database) String
full = proc (inp, db) -> do
    fo <- foo -< (inp, db)
    br <- bar -< (fo, db)
    bz <- baz -< (fo, db)
    id -< replicate br bz

Or, you can put them all under Reader and have the parameters pass implicitly:

fullR :: Auto (ReaderT Database m) Int String
fullR = proc inp -> do
    fo <- readerA foo -< inp
    br <- readerA bar -< fo
    bz <- readerA baz -< fo
    id -< replicate br bz

You can recover the original behavior of full by using runReaderA to "unroll" the implicit argument:

full' :: Auto m (Int, Database) String
full' = runReaderA fullR

You can also "seal" fullR so that it always runs with the same Database at every step using sealReader:

fullSealed :: Database -> Auto m Int String
fullSealed = sealReader fullR

fullSealed db will now assume that foo, bar, and baz all get the same environment forever when they are stepped/streamed.

runReaderA Source

Arguments

:: Monad m 
=> Auto (ReaderT r m) a b

Auto run over global environment

-> Auto m (a, r) b

Auto receiving environments

Unrolls the underlying ReaderT of an Auto into an Auto that takes in the input "environment" every turn in addition to the normal input.

So you can use any ReaderT r m as if it were an m. Useful if you want to compose and create some isolated Autos with access to an underlying environment, but not your entire program.

Also just simply useful as a convenient way to use an Auto over Reader with stepAuto and friends.

When used with Reader r, it turns an Auto (Reader r) a b into an Auto' (a, r) b.

sealReader Source

Arguments

:: (Monad m, Serialize r) 
=> Auto (ReaderT r m) a b

Auto run over Reader

-> r

the perpetual environment

-> Auto m a b 

Takes an Auto that operates under the context of a read-only environment, an environment value, and turns it into a normal Auto that always "sees" that value when it asks for one.

>>> let a   = effect ask :: Auto (Reader b) a b
>>> let rdr = streamAuto' a [1..5] :: Reader b [b]
>>> runReader rdr "hey"
["hey", "hey", "hey", "hey", "hey"]

Useful if you wanted to use it inside/composed with an Auto that does not have a global environment:

bar :: Auto' Int String
bar = proc x -> do
    hey <- sealReader (effect ask) "hey" -< ()
    id -< hey ++ show x
>>> streamAuto' bar [1..5]
["hey1", "hey2", "hey3", "hey4", "hey5"]

Note that this version serializes the given r environment, so that every time the Auto is reloaded/resumed, it resumes with the originally given r environment, ignoring whatever r is given to it when trying to resume it. If this is not the behavior you want, use sealReader_.

Reader is convenient because it allows you to "chain" and "compose" Autos with a common environment, instead of explicitly passing in values every time. For a convenient way of generating Autos under ReaderT, and also for some motivating examples, see readerA and runReaderA.

sealReader_ Source

Arguments

:: Monad m 
=> Auto (ReaderT r m) a b

Auto run over Reader

-> r

the perpetual environment

-> Auto m a b 

The non-resuming/non-serializing version of sealReader. Does not serialize/reload the r environment, so that whenever you "resume" the Auto, it uses the new r given when you are trying to resume, instead of loading the originally given one.

readerA Source

Arguments

:: Monad m 
=> Auto m (a, r) b

Auto receiving an environment.

-> Auto (ReaderT r m) a b

Auto run over an environment.

Transforms an Auto on two input streams ( a "normal input" stream a and an "environment input stream" r) into an Auto on one input stream a with an underlying environment r through a Reader monad.

Why is this useful? Well, if you have several Autos that all take in a side r stream, and you want to convey that every single one should get the same r at every step, you can instead have all of them pull from a common underlying global environment.

Note: Function is the inverse of runReaderA:

readerA . runReaderA == id
runReaderA . readerA == id

WriterT

WriterT gives you a shared "sink" to dump data into. You can dump in data by using

arrM tell :: MonadWriter w m => Auto m w ()
effect . tell :: MonadWriter w m => w -> Auto m a ()
foo :: Auto (Writer (Sum Int)) Int Int
foo = effect (tell 1) *> effect (tell 1) *> sumFrom 0
>>> let fooWriter = streamAuto foo
>>> runWriter $ fooWriter [1..10]
([1,3,6,10,15,21,28,36,45,55], Sum 20)

foo increments an underlying counter twice every time it is stepped; its "result" is just the cumulative sum of the inputs.

If you have several Autos that all output some "side-channel" value that is just all accumulated at the end, and you want to implicitly accumulate it all, you can just have them all dump into an underlying Writer sink instead of aggregating them explicitly.

For example:

foo :: Auto m Int (Bool, [String])
bar :: Auto m Bool (Int, [String])
baz :: Auto m Bool (String, [String])

Each of these has a "logging output" that should be aggregated all at the end.

One way you can do this is by using an explicit proc block:

full :: Auto m Int (String, [String])
full = proc inp -> do
    x <- sumFrom 0 -< inp
    (fo, foW) <- foo -< inp + x
    (br, brW) <- bar -< fo
    (bz, bzW) <- baz -< fo
    id -br bz, foW < brW <> bzW)

Or, you can handle the extra output implicitly using writerA:

fullW :: Auto (WriterT [String] m) Int String
fullW = proc inp -> do
    x  <- sumFrom 0   -< inp
    fo <- writerA foo -< inp + x
    br <- writerA bar -< fo
    bz <- writerA baz -< fo
    id -< replicate br bz

Note that sumFrom 0 still works the same and doesn't interfere, logging nothing.

You can recover the original full with runWriterA, which "unwraps" the underlying Writer:

full' :: Auto m Int (String, [String])
full' = runWriterA fullW

writerA Source

Arguments

:: (Monad m, Monoid w) 
=> Auto m a (b, w)

Auto with a "normal" output stream bs and a "logging" stream ws

-> Auto (WriterT w m) a b

Auto under an underlying WriterT, logging ws

Transforms an Auto on with two output streams (a "normal output stream" b, and a "logging output stream" w) into an Auto with just one output stream a, funneling the logging stream w into an underlying WriterT monad.

Note: Function is the inverse of runWriterA:

writerA . runWriterA == id
runWriterA . writerA == id

runWriterA :: (Monad m, Monoid w) => Auto (WriterT w m) a b -> Auto m a (b, w) Source

Unrolls the underlying WriterT w m Monad, so that an Auto that takes in a stream of a and outputs a stream of b will now output a stream (b, w), where w is the "new log" of the underlying Writer at every step.

Examples:

foo :: Auto (Writer (Sum Int)) Int Int
foo = effect (tell 1) *> effect (tell 1) *> sumFrom 0
>>> let fooWriter = streamAuto foo
>>> runWriter $ fooWriter [1..10]
([1,3,6,10,15,21,28,36,45,55], Sum 20)

foo increments an underlying counter twice every time it is stepped; its "result" is just the cumulative sum of the inputs.

When we "stream" it, we get a [Int] -> Writer (Sum Int) [Int]...which we can give an input list and runWriter it, getting a list of outputs and a "final accumulator state" of 10, for stepping it ten times.

However, if we use runWriterA before streaming it, we get:

>>> let fooW = runWriterA foo
>>> streamAuto' fooW [1..10]
[ (1 , Sum 2), (3 , Sum 2), (6 , Sum 2)
, (10, Sum 2), (15, Sum 2), (21, Sum 2), -- ...

Instead of accumulating it between steps, we get to "catch" the Writer output at every individual step.

We can write and compose our own Autos under Writer, using the convenience of a shared accumulator, and then "use them" with other Autos:

bar :: Auto' Int Int
bar = proc x -> do
  (y, w) <- runWriterA foo -< x
  blah <- blah -< w

And now you have access to the underlying accumulator of foo to access. There, w represents the continually updating accumulator under foo, and will be different/growing at every "step".

For a convenient way to create an Auto under WriterT, see writerA.

StateT

An underlying StateT gives you access to a global, mutable state.

At first this might be seem a little silly. We went through all this trouble to avoid the headache of global mutable state, and now we add ti back in?

One nice usage is an underlying entropy generator (you can deal with this more explicitly with sealRandom in Control.Auto.Process.Random), or maybe some underlying pool that every Auto shares that would be a big headache to thread manually.

The main benefit here is that, using tools like sealState and runStateA, we can isolate the portion of our program that takes advantage of shared mutable state, and seal off or only give that part access to the state... and nobody else.

Anyways, it should go without saying that you should think really long and really hard before adding in global state to your program. It is almost always better to use principles of local statefulness and denotative composition to achieve what you want. Relying on this construct might lead to very unmaintainable code, and definitely code that is much more difficult to reason with. I suggest trying to find another solution first in all cases!

sealState Source

Arguments

:: (Monad m, Serialize s) 
=> Auto (StateT s m) a b

Auto run over State

-> s

initial state

-> Auto m a b 

Takes an Auto that works with underlying global, mutable state, and "seals off the state" from the outside world.

An 'Auto (StateT s m) a b' maps a stream of a to a stream of b, but does so in the context of requiring an initial s to start, and outputting a modified s.

Consider this example State Auto:

foo :: Auto (State s) Int Int
foo = proc x -> do
    execB (modify (+1)) . emitOn odd  -< x
    execB (modify (*2)) . emitOn even -< x
    st   <- effect get -< ()
    sumX <- sumFrom 0  -< x
    id    -< sumX + st

On every output, the "global" state is incremented if the input is odd and doubled if the input is even. The stream st is always the value of the global state at that point. sumX is the cumulative sum of the inputs. The final result is the sum of the value of the global state and the cumulative sum.

In writing like this, you lose some of the denotative properties because you are working with a global state that updates at every output. You have some benefit of now being able to work with global state, if that's what you wanted I guess.

To "run" it, you could use streamAuto to get a State Int Int:

>>> let st = streamAuto foo [1..10] :: State Int Int
>>> runState st 5
([  7, 15, 19, 36, 42, 75, 83,136,156,277], 222)

(The starting state is 5 and the ending state after all of that is 222)

However, writing your entire program with global state is a bad bad idea! So, how can you get the "benefits" of having small parts like foo be written using State, and being able to use it in a program with no global state?

Using sealState! Write the part of your program that would like shared global state with State...and compose it with the rest as if it doesn't, locking it away!

sealState       :: Auto (State s) a b -> s -> Auto' a b
sealState foo 5 :: Auto' Int Int
bar :: Auto' Int (Int, String)
bar = proc x -> do
    food <- sealState foo 5 -< x
    id -< (food, show x)
>>> streamAuto' bar [1..10]
[ (7, "1"), (15, "2"), (19, "3"), (36, "4"), (42, "5"), (75, "6") ...

We say that sealState f s0 takes an input stream, and the output stream is the result of running the stream through f, first with an initial state of s0, and afterwards with each next updated state.

For a convenient way of "creating" an Auto under StateT in the first place, see stateA.

sealState_ Source

Arguments

:: Monad m 
=> Auto (StateT s m) a b

Auto run over State

-> s

initial state

-> Auto m a b 

The non-resuming/non-serializing version of sealState.

runStateA Source

Arguments

:: Monad m 
=> Auto (StateT s m) a b

Auto run over a state transformer

-> Auto m (a, s) (b, s)

Auto whose inputs and outputs are a state transformer

Unrolls the underlying StateT of an Auto into an Auto that takes in an input state every turn (in addition to the normal input) and outputs, along with the original result, the modified state.

So now you can use any StateT s m as if it were an m. Useful if you want to compose and create some isolated Autos with access to an underlying state, but not your entire program.

Also just simply useful as a convenient way to use an Auto over State with stepAuto and friends.

When used with State s, it turns an Auto (State s) a b into an Auto' (a, s) (b, s).

For a convenient way to "generate" an Auto StateT, see stateA

stateA Source

Arguments

:: Monad m 
=> Auto m (a, s) (b, s)

Auto whose inputs and outputs are a state transformer

-> Auto (StateT s m) a b

Auto run over a state transformer

Transforms an Auto with two input streams and two output streams (a "normal" input a output b stream, and a "state transforming" side-stream taking in s and outputting s), abstracts away the s stream as a modifcation to an underyling StateT monad. That is, your normal inputs and outputs are now your only inputs and outputs, and your input s comes from the underlying global mutable state, and the output s goes to update the underlying global mutable state.

For example, you might have a bunch of Autos that interact with a global mutable state:

foo :: Auto (StateT Double m) Int Bool
bar :: Auto (StateT Double m) Bool Int
baz :: Auto (StateT Double m) Bool String

Where foo, bar, and baz all interact with global mutable state. You'd use them like this:

full :: Auto (StateT Double m) Int String
full = proc inp -> do
    fo <- foo -< inp
    br <- bar -< fo
    bz <- baz -< fo
    id -< replicae br bz

stateA allows you generate a new Auto under StateT:

thing :: Auto m (Int, Double) (Bool, Double)
stateA thing :: Auto (StateT Double m) Int Bool

So now the two side-channels are interpreted as working with the global state:

full :: Auto (StateT Double m) Int String
full = proc inp -> do
    fo <- foo          -< inp
    tg <- stateA thing -< inp
    br <- bar          -< fo || tg
    bz <- baz          -< fo && tg
    id -< replicae br bz

You can then "seal it all up" in the end with an initial state, that keeps on re-running itself with the resulting state every time:

full' :: Double -> Auto m Int String
full' = sealState full

Admittedly, this is a bit more esoteric and dangerous (programming with global state? what?) than its components readerA and writerA; I don't actually recommend you programming with global state unless it really is the best solution to your problem...it tends to encourage imperative code/loops, and "unreasonable" and manageable code. See documentation for sealStateA for best practices. Basically every bad thing that comes with global mutable state. But, this is provided here for sake of completeness with readerA and writerA.

Note: function is the inverse of runstateA.

stateA . runStateA == id
runStateA . stateA == id

accumA Source

Arguments

:: Monad m 
=> Auto m (a, s) s

Auto taking inputs and states and returning updated states

-> Auto (StateT s m) a s

Auto over a state transformer

Like stateA, but assumes that the output is the modified state.

Traversable

runTraversableA Source

Arguments

:: (Monad f, Traversable f) 
=> Auto f a b

Auto run over traversable structure

-> Auto m a (f b)

Auto returning traversable structure

Unrolls the underlying Monad of an Auto if it happens to be Traversable ('[]', Maybe, etc.).

It can turn, for example, an Auto [] a b into an Auto' a [b]; it collects all of the results together. Or an Auto Maybe a b into an Auto' a (Maybe b).

This might be useful if you want to make some sort of "underlying inhibiting" Auto where the entire computation might just end up being Nothing in the end. With this, you can turn that possibly-catastrophically-failing Auto (with an underlying Monad of Maybe) into a normal Auto, and use it as a normal Auto in composition with other Autos...returning Just if your computation succeeded.

runTraversableA :: Auto Maybe a b -> Interval' a b
foo :: Auto Maybe Int Int
foo = arrM $ x -> if even x then Just (x div 2) else Nothing

bar :: Auto Maybe Int Int
bar = arrM Just
>>> streamAuto (foo &&& bar) [2,4,6]
Just [(1, 2),(2, 4),(3, 6)]
>>> streamAuto (foo &&& bar) [2,4,6,7]
Nothing
>>> streamAuto' ('runTraversableA' foo '<|?>' 'runTraversableA' bar) [2,4,6,7]
[Just 1, Just 2, Just 3, Just 7]

IO

catchA Source

Arguments

:: Exception e 
=> Auto IO a b

Auto over IO, expecting an exception of a secific type.

-> Auto IO a (Either e b) 

Wraps a "try" over an underlying IO monad; if the Auto encounters a runtime exception while trying to "step" itself, it'll output a Left with the Exception. Otherwise, will output left.

Note that you have to explicitly specify the type of the exceptions you are catching; see Control.Exception documentation for more details.