Copyright | (c) Justin Le 2015 |
---|---|
License | MIT |
Maintainer | justin@jle.im |
Stability | unstable |
Portability | portable |
Safe Haskell | None |
Language | Haskell2010 |
- arrM :: (a -> m b) -> Auto m a b
- effect :: m b -> Auto m a b
- effects :: Monad m => Auto m (m a) a
- arrMB :: Monad m => (a -> m b) -> Auto m (Blip a) (Blip b)
- effectB :: Monad m => m b -> Auto m (Blip a) (Blip b)
- execB :: Monad m => m b -> Auto m (Blip a) (Blip a)
- cache :: (Serialize b, Monad m) => m b -> Auto m a b
- execOnce :: Monad m => m b -> Auto m a ()
- cache_ :: Monad m => m b -> Auto m a b
- execOnce_ :: Monad m => m b -> Auto m a ()
- hoistA :: (Monad m, Monad m') => (forall c. m c -> m' c) -> Auto m a b -> Auto m' a b
- generalizeA :: Monad m => Auto' a b -> Auto m a b
- runReaderA :: Monad m => Auto (ReaderT r m) a b -> Auto m (a, r) b
- sealReader :: (Monad m, Serialize r) => Auto (ReaderT r m) a b -> r -> Auto m a b
- sealReader_ :: Monad m => Auto (ReaderT r m) a b -> r -> Auto m a b
- readerA :: Monad m => Auto m (a, r) b -> Auto (ReaderT r m) a b
- sealReaderMVar :: MonadIO m => Auto (ReaderT r m) a b -> MVar r -> Auto m a b
- sealReaderM :: Monad m => Auto (ReaderT r m) a b -> m r -> Auto m a b
- writerA :: (Monad m, Monoid w) => Auto m a (b, w) -> Auto (WriterT w m) a b
- runWriterA :: (Monad m, Monoid w) => Auto (WriterT w m) a b -> Auto m a (b, w)
- sealState :: (Monad m, Serialize s) => Auto (StateT s m) a b -> s -> Auto m a b
- sealState_ :: Monad m => Auto (StateT s m) a b -> s -> Auto m a b
- runStateA :: Monad m => Auto (StateT s m) a b -> Auto m (a, s) (b, s)
- stateA :: Monad m => Auto m (a, s) (b, s) -> Auto (StateT s m) a b
- accumA :: Monad m => Auto m (a, s) s -> Auto (StateT s m) a s
- runTraversableA :: (Monad f, Traversable f) => Auto f a b -> Auto m a (f b)
- catchA :: Exception e => Auto IO a b -> Auto IO a (Either e b)
Running effects
Continually
:: (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"
...except, when you step it,
it prints out to stdout as a side-effect. We just gave automatic
stdout logging behavior!sumFrom
0
:: 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 beffect
:: 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
, except at each step,
it also increments the underlying/global state by one. It is sumFrom
0
with an "attached effect".sumFrom
0
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 Blip
s
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
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")
Always outputs '()', but when asked for the first output, executes the given monadic action.
Pretty much like cache
, but always outputs '()'.
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")
Hoists
:: (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
Specific underlying monads
Auto
s 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 Auto
s with
a static guarantee that all Auto
s 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.
:: Monad m | |
=> Auto (ReaderT r m) a b |
|
-> Auto m (a, r) b |
|
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
as if it were an ReaderT
r mm
. Useful if you
want to compose and create some isolated Auto
s 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
, it turns an Reader
r
into
an Auto
(Reader
r) a b
.Auto'
(a, r) b
:: (Monad m, Serialize r) | |
=> Auto (ReaderT r m) a b |
|
-> 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"
Auto
s with a common environment, instead of explicitly passing in
values every time. For a convenient way of generating Auto
s under
ReaderT
, and also for some motivating examples, see readerA
and
runReaderA
.
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.
DOES serialize the actual Auto
!
:: Monad m | |
=> Auto m (a, r) b |
|
-> Auto (ReaderT r m) a b |
|
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 Auto
s 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
Sealing from sources
:: MonadIO m | |
=> Auto (ReaderT r m) a b |
|
-> MVar r |
|
-> 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 gets its environment value from an MVar
.
This allows for "hot swapping" configurations. If your whole program
runs under a configuration data structure as the environment, you can
load the configuration data to the MVar
and then "hot swap" it out by
just changing the value in the MVar
from a different thread.
Note that this will block on every "step" until the MVar
is
readablefullhas a value, if it does not.
Basically a disciplined wrapper/usage over sealReaderM
.
:: Monad m | |
=> Auto (ReaderT r m) a b |
|
-> m r | action to draw new |
-> 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 gets its environment value by executing an action every step
in the underlying monad.
This can be abused to write unreadable code really fast if you don't use
it in a disciplined way. One possible usage is to query a database in
IO
(or MonadIO
) for a value at every step. If you're using
underlying global state, you can use it to query that too, with get
or
gets
. You could even use getLine
, maybe, to get the result from
standard input at every step.
One disciplined wrapper around this is sealReaderMVar
, where the
environment at every step comes from reading an MVar
. This can be
used to "hot swap" configuration files.
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 Auto
s 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
still works the same and doesn't interfere,
logging nothing.sumFrom
0
You can recover the original full
with runWriterA
, which
"unwraps" the underlying Writer
:
full' :: Auto m Int (String, [String]) full' = runWriterA fullW
:: (Monad m, Monoid w) | |
=> Auto m a (b, w) |
|
-> Auto (WriterT w m) a b |
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 mMonad
, 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] ->
...which we can give an input list and Writer
(Sum Int)
[Int]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 Auto
s under Writer
, using the
convenience of a shared accumulator, and then "use them" with other
Auto
s:
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!
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
takes an input stream, and the output
stream is the result of running the stream through sealState
f s0f
, 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
.
The non-resuming/non-serializing version of sealState
.
:: Monad m | |
=> Auto (StateT s m) a b |
|
-> Auto m (a, s) (b, s) |
|
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
as if it were an StateT
s mm
. Useful if
you want to compose and create some isolated Auto
s 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
, it turns an State
s
into an
Auto
(State
s) a b
.Auto'
(a, s) (b, s)
For a convenient way to "generate" an Auto
StateT
, see stateA
:: Monad m | |
=> Auto m (a, s) (b, s) |
|
-> Auto (StateT s m) a b |
|
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 Auto
s 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
:: Monad m | |
=> Auto m (a, s) s |
|
-> Auto (StateT s m) a s |
|
Like stateA
, but assumes that the output is the modified state.
Traversable
:: (Monad f, Traversable f) | |
=> Auto f a b |
|
-> Auto m a (f b) |
|
Unrolls the underlying Monad
of an Auto
if it happens to be
Traversable
('[]', Maybe
, etc.).
It can turn, for example, an
into an Auto
[] a b
; it
collects all of the results together. Or an Auto'
a [b]
into
an Auto
Maybe
a b
.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 Auto
s...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
:: 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.