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