bicoro/coroutine.rs
1/// A structure describing a co-routine supporting sends (inputs),
2/// yields (outputs), and a final termination (result)
3///
4/// This requires something else to execute it.
5/// a send or a yield will 'pause' the coroutine until the executor provides
6/// or consumes the output.
7///
8/// This structure is useful as you can logically describe a workflow,
9/// but leave the 'plumbing' of IO to later.
10///
11/// A simple case of input an output would be using enums.
12/// So that you can send and recieve different messages
13///
14/// This is represented as a monad <https://en.wikipedia.org/wiki/Monad_(functional_programming)>
15pub struct Coroutine<'a, Input, Output, Result> {
16 resume: CoroutineState<'a, Input, Output, Result>,
17}
18
19// I believe this is safe, as the closures are send and fnonce.
20// the coroutine can only be changed by move, so multiple aliases
21// shouldn't be a problem. Would love to have feedback on this
22unsafe impl<'a, I, O, R> Sync for Coroutine<'a, I, O, R> {}
23
24/// The internal state of the machine
25enum CoroutineState<'a, Input: 'a, Output: 'a, Result: 'a> {
26 /// The coroutine is paused waiting for some-input
27 Await(Box<dyn FnOnce(Input) -> Coroutine<'a, Input, Output, Result> + Send + 'a>),
28 /// The coroutine is paused, waiting for a output to be consumed
29 Yield(
30 Output,
31 Box<dyn FnOnce() -> Coroutine<'a, Input, Output, Result> + Send + 'a>,
32 ),
33 /// The coroutine is completed
34 Done(Result),
35}
36
37/// Return/unit. Creates a result of the supplied value
38///
39/// This lifts the value into the coroutine 'world'
40/// ```
41/// use bicoro::*;
42/// let co :Coroutine<(),(),i32> = result(1);
43/// ```
44pub fn result<'a, I, O, R>(r: R) -> Coroutine<'a, I, O, R> {
45 let resume = CoroutineState::Done(r);
46 Coroutine { resume }
47}
48
49/// Suspend this coroutine until an input arrives with a function
50///
51/// The function f, will be called on this input
52/// see also: recieve()
53/// ```
54/// use bicoro::*;
55/// let co :Coroutine<i32,(),String> = suspend(Box::new(|input:i32| result(input.to_string())));
56/// ```
57pub fn suspend<'a, I, O, R, F>(f: F) -> Coroutine<'a, I, O, R>
58where
59 F: FnOnce(I) -> Coroutine<'a, I, O, R> + Send + 'a,
60{
61 let closure = Box::new(f);
62 let resume = CoroutineState::Await(closure);
63 Coroutine { resume }
64}
65
66/// Yields a value to the executor
67///
68/// This pauses until the executor uses it
69/// ```
70/// use bicoro::*;
71/// let co :Coroutine<(),&str,()> = send("hello");
72/// ```
73pub fn send<'a, I, O>(o: O) -> Coroutine<'a, I, O, ()> {
74 let resume = CoroutineState::Yield(o, Box::new(|| result(())));
75 Coroutine { resume }
76}
77
78/// Chain outputs together.
79///
80/// This allows the results from one item
81/// to flow into the next one. This can call any arbitrary co-routine
82/// The next routine needs the same inputs and outputs, but can change it's result
83/// This is equivalent to and_then for the Future type.
84/// ```
85/// use bicoro::*;
86/// // reads two input values and adds them.
87/// let co:Coroutine<i32,(),i32> = bind(receive(),|a:i32| bind(receive(), move |b:i32| result(a+b)));
88/// ```
89pub fn bind<'a, I, O, A, B, F>(m: Coroutine<'a, I, O, A>, f: F) -> Coroutine<'a, I, O, B>
90where
91 F: FnOnce(A) -> Coroutine<'a, I, O, B> + Send + 'a,
92{
93 match m.resume {
94 CoroutineState::Done(ra) => f(ra),
95 CoroutineState::Yield(output, ra) => {
96 let state = || bind(ra(), f);
97 let resume = CoroutineState::Yield(output, Box::new(state));
98 Coroutine { resume }
99 }
100 CoroutineState::Await(mf) => {
101 let state = |input: I| -> Coroutine<I, O, B> {
102 let next = mf(input);
103 bind(next, f)
104 };
105 suspend(state)
106 }
107 }
108}
109
110/// A step wise evalution of the coroutine
111///
112/// this allows you to 'iterate' through until you need to provide input
113/// or to observe yields.
114///
115/// In the cause of input, a function is returned, it's expected
116/// the executor will call this with the input.
117///
118/// In the cause of output, a tuple of the output and the remaining coroutine
119/// is returned.
120///
121/// In the coroutine is finished, it will be in the done case, so the return
122/// value can be extracted
123
124pub enum StepResult<'a, Input, Output, Result> {
125 /// The final value
126 Done(Result),
127 /// We have output to give to the executor
128 Yield {
129 /// The current output being provided to the executor
130 output: Output,
131 /// The remaining coroutine to process
132 next: Box<Coroutine<'a, Input, Output, Result>>,
133 },
134 /// The coroutine is suspended, awaiting input
135 Next(Box<dyn FnOnce(Input) -> Coroutine<'a, Input, Output, Result> + Send + 'a>),
136}
137
138/// Runs a single step in the coroutine.
139///
140/// This returns the step result. Used to interpret/run the coroutine
141/// ```
142/// use bicoro::*;
143/// let co: Coroutine<i32,(),i32> = receive();
144/// let sr = run_step(co);
145/// assert!(matches!(sr, StepResult::Next(_)));
146/// ```
147pub fn run_step<I, O, R>(routine: Coroutine<I, O, R>) -> StepResult<I, O, R> {
148 match routine.resume {
149 CoroutineState::Done(result) => StepResult::Done(result),
150 CoroutineState::Await(run) => StepResult::Next(run),
151 CoroutineState::Yield(output, next) => StepResult::Yield {
152 output,
153 next: Box::new(next()),
154 },
155 }
156}