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.Serialize

Contents

Description

This module provides tools for working with the automatically derived serializability and resumability of Autos. The first half contains boring wrappers around encoding and decoding to and from binary, filepaths on disk, etc.

The second half contains Auto transformers that "imbue" an Auto with IO serialization abilities. Note that these all require an underlying Monad that is an instance of MonadIO.

You have "identity-like" transformers that take an Auto and spit it back out operationally unchanged...but every step, it might do some behind-the-scenes saving or re-load itself from disk when it is first stepped. Or you have some "trigger enhancers" that take normal Autos and give you the ability to "trigger" saving and loading events on the Auto using the Blip mechanisms and blip stream semantics from Control.Auto.Blip.

Note that the entire Auto construct is a little bit awkward when it comes to performing IO effects --- it isn't exactly what they were designed for originally. Hooking on effects to stepping can be powerful, but as of now, not much has been looked into meaningful error handling when working with IO. If you have any experience with this and are willing to help, please feel free to send me an e-mail or open an issue on the issue tracker!

Synopsis

Serializing and deserializing Autos

To and from Data.Serialize types

saveAuto :: Auto m a b -> Put Source

Returns a Put --- instructions (from Data.Serialize) on how to "freeze" the Auto, with its internal state, and save it to a binary encoding. It can later be reloaded and "resumed" by 'resumeAuto'/'decodeAuto'.

resumeAuto :: Auto m a b -> Get (Auto m a b) Source

Returns a Get from an Auto --- instructions (from Data.Serialize) on taking a ByteString and "restoring" the originally saved Auto, in the originally saved state.

To and from binary

encodeAuto :: Auto m a b -> ByteString Source

Encode an Auto and its internal state into a ByteString.

decodeAuto :: Auto m a b -> ByteString -> Either String (Auto m a b) Source

Resume an Auto from its ByteString serialization, giving a Left if the deserialization is not possible.

To and from disk

writeAuto Source

Arguments

:: FilePath

filepath to write to

-> Auto m a b

Auto to serialize

-> IO () 

Given a FilePath and an Auto, serialize and freeze the state of the Auto as binary to that FilePath.

readAuto Source

Arguments

:: FilePath

filepath to read from

-> Auto m a b

Auto to resume

-> IO (Either String (Auto m a b)) 

Give a FilePath and an Auto, and readAuto will attempt to resume the saved state of the Auto from disk, reading from the given FilePath. Will return Left upon a decoding error, with the error, and Right if the decoding is succesful.

readAutoDef Source

Arguments

:: FilePath

filepath to read from

-> Auto m a b

Auto to resume

-> IO (Either String (Auto m a b)) 

Like readAuto, but will return the original Auto (instead of a resumed one) if the file does not exist.

Useful if you want to "resume an Auto" "if there is" a save state, or just use it as-is if there isn't.

Imbuing Autos with serialization

Implicit automatic serialization

saving Source

Arguments

:: MonadIO m 
=> FilePath

filepath to write to

-> Auto m a b

Auto to transform

-> Auto m a b 

Transforms the given Auto into an Auto that constantly saves its state to the given FilePath at every "step". Requires an underlying MonadIO.

Note that (unless the Auto depends on IO), the resulting Auto is meant to be operationally identical in its inputs/outputs to the original one.

loading' Source

Arguments

:: MonadIO m 
=> FilePath

filepath to read from

-> Auto m a b

Auto to transform (or return unchanged)

-> Auto m a b 

Like loading, except silently suppresses all I/O and decoding errors; if there are errors, it returns back the given Auto as-is.

Useful for when you aren't sure the save state is on disk or not yet, and want to resume it only in the case that it is.

loading Source

Arguments

:: MonadIO m 
=> FilePath

filepath to read from

-> Auto m a b

Auto to transform

-> Auto m a b 

Transforms the given Auto into an Auto that, when you first try to run or step it, "loads" itself from disk at the given FilePath.

Will throw a runtime exception on either an I/O error or a decoding error.

Note that (unless the Auto depends on IO), the resulting Auto is meant to be operationally identical in its inputs/outputs to the fast-forwarded original Auto.

serializing' Source

Arguments

:: MonadIO m 
=> FilePath

filepath to read from and write to

-> Auto m a b

Auto to transform

-> Auto m a b 

Like serializing, except suppresses all I/O and decoding errors.

Useful in the case that when the Auto is first run and there is no save state yet on disk (or the save state is corrupted), it'll "start a new one"; if there is one, it'll load it automatically. Then, on every further step in both cases, it'll update the save state.

serializing Source

Arguments

:: MonadIO m 
=> FilePath

filepath to read from and write to

-> Auto m a b

Auto to transform

-> Auto m a b 

A combination of saving and loading. When the Auto is first run, it loads the save state from the given FilePath and fast forwards it. Then, subsequently, it updates the save state on disk on every step.

Triggered (blip stream-based) automatic serialization

Note that these follow the naming conventions from Control.Auto.Switch: Something "from" a blip stream is a thing triggered by the Auto itself, and something "on" a blip stream is a thing triggered externally, from another Auto.

saveOnB Source

Arguments

:: MonadIO m 
=> Auto m a b

Auto to make saveable-by-trigger

-> Auto m (a, Blip FilePath) b 

Takes an Auto and basically "wraps" it so that you can trigger saves with a blip stream.

For example, we can take sumFrom 0:

saveOnB (sumFrom 0) :: Auto IO (Int, Blip FilePath) Int

It'll behave just like sumFrom 0 (with the input you pass in the first field of the tuple)...and whenever the blip stream (the second field of the input tuple) emits, it'll save the state of sumFrom 0 to disk at the given FilePath.

Contrast to saveFromB, where the Auto itself can trigger saves; in this one, saves are triggered "externally".

Might be useful in similar situations as saveFromB, except if you want to trigger the save externally.

loadOnB' Source

Arguments

:: MonadIO m 
=> Auto m a b

Auto to make reloadable-by-trigger

-> Auto m (a, Blip FilePath) b 

Like loadOnB, except silently ignores errors. When a load is requested, but there is an IO or parse error, the loading is skipped.

loadOnB Source

Arguments

:: MonadIO m 
=> Auto m a b

Auto to make reloadable-by-trigger

-> Auto m (a, Blip FilePath) b 

Takes an Auto and basically "wraps" it so that you can trigger loads/resumes from a file with a blip stream.

For example, we can take sumFrom 0:

loadOnB (sumFrom 0) :: Auto IO (Int, Blip FilePath) Int

It'll behave just like sumFrom 0 (with the input you pass in the first field of the tiple)...and whenever the blip stream (the second field of the input tuple) emits, it'll "reset" and "reload" the sumFrom 0 from the FilePath on disk.

Will throw a runtime exception if there is an IO error or a parse error.

Contrast to loadFromB, where the Auto itself can trigger reloads/resets; in this one, the loads are triggered "externally".

Might be useful in similar situations as loadFromB, except if you want to trigger the loading externally.

Intrinsic triggering

saveFromB Source

Arguments

:: MonadIO m 
=> Auto m a (b, Blip FilePath)

Auto producing a value b and a blip stream with a FilePath to save to

-> Auto m a b 

Takes an Auto that produces a blip stream with a FilePath and a value, and turns it into an Auto that, outwardly, produces just the value.

Whenever the output blip stream emits, it automatically serializes and saves the state of the Auto to the emitted FilePath.

In practice, this allows any Auto to basically control when it wants to "save", by providing a blip stream.

The following is an alternative implementation of saving, except saving every two steps instead of every step:

saving2 fp a = saveFromB (a &&& (every 2 . pure fp))

Or, in proc notation:

saving2 fp a = saveFromB $ proc x -> do
    y <- a       -< x
    b <- every 2 -< fp
    id -< (y, b)

(Recall that every n is the Auto that emits the received value every n steps)

In useful real-world cases, you can have the Auto decide whether or not to save itself based on its input. Like, for example, when it detects a certain user command, or when the user has reached a given location.

The following takes a FilePath and an Auto (a), and turns it into an Auto that "saves" whenever a crosses over from positive to negative.

saveOnNegative fp a = saveFromB $ proc x -> do
    y       <- a            -< x
    saveNow <- became (< 0) -< y
    id       -< (y, fp <$ saveNow)

Contrast to saveOnB, where the saves are triggered by outside input. In this case, the saves are triggered by the Auto to be saved itself.

loadFromB' Source

Arguments

:: MonadIO m 
=> Auto m a (b, Blip FilePath)

Auto with an output and a blip stream to trigger re-loading itself from the given filepath

-> Auto m a b 

Like loadFromB, except silently ignores errors. When a load is requested, but there is an IO or parse error, the loading is skipped.

loadFromB Source

Arguments

:: MonadIO m 
=> Auto m a (b, Blip FilePath)

Auto with an output and a blip stream to trigger re-loading itself from the given filepath

-> Auto m a b 

Takes an Auto that outputs a b and a blip stream of FilePaths and returns an Auto that ouputs only that b stream...but every time the blip stream emits, it "resets/loads" itself from that FilePath.

The following is a re-implementation of loading...except delayed by one (the second step that is run is the first "resumed" step).

loading2 fp a = loadFromB $ proc x -> do
    y       <- a           -< x
    loadNow <- immediately -< fp
    id       -< (y, loadNow)

(the blip stream emits only once, immediately, to re-load).

In the real world, you could have the Auto decide to reset or resume itself based on a user command:

loadFrom = loadFromB $ proc x -> do
    steps  <- count -< ()
    toLoad <- case words x of
                  ("load":fp:_) -> do
                      immediately -< fp
                  _             -> do
                      never       -< ()
    id      -< (steps, toLoad)

This will throw a runtime error on an IO exception or parsing error.