Skip to main content

behavior/calculus/
reducer.rs

1//! Pure reduction of transition actions and event streams.
2
3use core::ops::ControlFlow;
4
5use super::Behavior;
6use crate::Exit;
7use crate::actor::{Address, BirthMode, Create};
8use crate::effects::{Actions, SendAlgebra};
9use crate::next::{Never, Step};
10
11/// The accumulated observable effects of a transition prefix.
12pub struct Effects<A: Address, Sends, New> {
13    pub sends: Sends,
14    pub creates: Vec<Create<A, New>>,
15}
16
17/// The result of folding initialization and an event stream.
18pub struct Folded<A: Address, Sends, New> {
19    pub effects: Effects<A, Sends, New>,
20    pub exit: Option<Exit<A>>,
21    pub transitions: usize,
22}
23
24/// A left fold over Bombay actions.
25pub struct ActionReducer<A: Address, Sends, New> {
26    effects: Effects<A, Sends, New>,
27    transitions: usize,
28}
29
30impl<A: Address, Sends: SendAlgebra, New> Default for ActionReducer<A, Sends, New> {
31    fn default() -> Self {
32        Self {
33            effects: Effects {
34                sends: Sends::empty(),
35                creates: Vec::new(),
36            },
37            transitions: 0,
38        }
39    }
40}
41
42impl<A: Address, Sends: SendAlgebra, New> ActionReducer<A, Sends, New> {
43    #[must_use]
44    pub fn new() -> Self {
45        Self::default()
46    }
47
48    /// Append one action value. Order is preserved and the first stop verdict
49    /// short-circuits the surrounding fold.
50    pub fn push<Birth: BirthMode<Child = New>>(
51        &mut self,
52        actions: Actions<A, Never, Sends, Birth>,
53    ) -> ControlFlow<Exit<A>> {
54        self.transitions += 1;
55        self.effects.sends.append(actions.sends);
56        self.effects.creates.extend(actions.creates);
57        match actions.become_ {
58            Step::Continue => ControlFlow::Continue(()),
59            Step::Goto(never) => match never {},
60            Step::Stop(exit) => ControlFlow::Break(exit),
61        }
62    }
63
64    #[must_use]
65    pub fn finish(self, exit: Option<Exit<A>>) -> Folded<A, Sends, New> {
66        Folded {
67            effects: self.effects,
68            exit,
69            transitions: self.transitions,
70        }
71    }
72}
73
74/// Initialize a behavior and left-fold events until exhaustion, controlled
75/// failure, or the first stop verdict.
76///
77/// # Errors
78/// Returns the first controlled behavior failure.
79#[allow(
80    clippy::type_complexity,
81    reason = "the result exposes every behavior-owned effect and child seat"
82)]
83pub fn fold_events<B>(
84    behavior: &mut B,
85    events: impl IntoIterator<Item = B::Event>,
86) -> Result<Folded<B::Addr, B::Sends, <B::Birth as BirthMode>::Child>, B::Error>
87where
88    B: Behavior<Ph = Never>,
89{
90    let mut reducer = ActionReducer::new();
91    if let ControlFlow::Break(exit) = reducer.push(behavior.init()?) {
92        return Ok(reducer.finish(Some(exit)));
93    }
94
95    let result = events.into_iter().try_fold((), |(), event| {
96        let actions = match behavior.transition(event) {
97            Ok(actions) => actions,
98            Err(error) => return ControlFlow::Break(Err(error)),
99        };
100        match reducer.push(actions) {
101            ControlFlow::Continue(()) => ControlFlow::Continue(()),
102            ControlFlow::Break(exit) => ControlFlow::Break(Ok(exit)),
103        }
104    });
105
106    match result {
107        ControlFlow::Continue(()) => Ok(reducer.finish(None)),
108        ControlFlow::Break(Ok(exit)) => Ok(reducer.finish(Some(exit))),
109        ControlFlow::Break(Err(error)) => Err(error),
110    }
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116    use crate::{Acted, Actions, Delivery, MailAddr, NoBirths, Pure, Recipient, User};
117
118    #[test]
119    fn event_fold_short_circuits_and_accepts_capturing_transitions() {
120        let stop_at = 3;
121        let mut behavior = Pure::from_fn(
122            0_u8,
123            move |sum: &mut u8,
124                  _from: MailAddr,
125                  message: u8|
126                  -> Acted<
127                MailAddr,
128                Never,
129                Vec<Delivery<MailAddr, u8>>,
130                NoBirths,
131                Never,
132            > {
133                *sum += message;
134                let sends = vec![Delivery::new(Recipient::global(MailAddr(9)), *sum)];
135                Ok(Actions::new(
136                    sends,
137                    Vec::new(),
138                    if *sum >= stop_at {
139                        Step::Stop(Exit::Normal)
140                    } else {
141                        Step::Continue
142                    },
143                ))
144            },
145        );
146
147        let folded = fold_events(
148            &mut behavior,
149            [
150                User::new(MailAddr(1), 1),
151                User::new(MailAddr(1), 2),
152                User::new(MailAddr(1), 100),
153            ],
154        )
155        .unwrap();
156
157        assert_eq!(folded.transitions, 3); // initialization plus two events
158        assert_eq!(folded.effects.sends.len(), 2);
159        assert!(matches!(folded.exit, Some(Exit::Normal)));
160        assert_eq!(behavior.state().state, 3);
161    }
162}