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.
79pub fn fold_events<B>(
80    behavior: &mut B,
81    events: impl IntoIterator<Item = B::Event>,
82) -> Result<Folded<B::Addr, B::Sends, <B::Birth as BirthMode>::Child>, B::Error>
83where
84    B: Behavior<Ph = Never>,
85{
86    let mut reducer = ActionReducer::new();
87    if let ControlFlow::Break(exit) = reducer.push(behavior.init()?) {
88        return Ok(reducer.finish(Some(exit)));
89    }
90
91    let result = events.into_iter().try_fold((), |(), event| {
92        let actions = match behavior.transition(event) {
93            Ok(actions) => actions,
94            Err(error) => return ControlFlow::Break(Err(error)),
95        };
96        match reducer.push(actions) {
97            ControlFlow::Continue(()) => ControlFlow::Continue(()),
98            ControlFlow::Break(exit) => ControlFlow::Break(Ok(exit)),
99        }
100    });
101
102    match result {
103        ControlFlow::Continue(()) => Ok(reducer.finish(None)),
104        ControlFlow::Break(Ok(exit)) => Ok(reducer.finish(Some(exit))),
105        ControlFlow::Break(Err(error)) => Err(error),
106    }
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112    use crate::{Acted, Actions, Delivery, MailAddr, NoBirths, Pure, Recipient, User};
113
114    #[test]
115    fn event_fold_short_circuits_and_accepts_capturing_transitions() {
116        let stop_at = 3;
117        let mut behavior = Pure::from_fn(
118            0_u8,
119            move |sum: &mut u8,
120                  _from: MailAddr,
121                  message: u8|
122                  -> Acted<
123                MailAddr,
124                Never,
125                Vec<Delivery<MailAddr, u8>>,
126                NoBirths,
127                Never,
128            > {
129                *sum += message;
130                let sends = vec![Delivery::new(Recipient::global(MailAddr(9)), *sum)];
131                Ok(Actions::new(
132                    sends,
133                    Vec::new(),
134                    if *sum >= stop_at {
135                        Step::Stop(Exit::Normal)
136                    } else {
137                        Step::Continue
138                    },
139                ))
140            },
141        );
142
143        let folded = fold_events(
144            &mut behavior,
145            [
146                User::new(MailAddr(1), 1),
147                User::new(MailAddr(1), 2),
148                User::new(MailAddr(1), 100),
149            ],
150        )
151        .unwrap();
152
153        assert_eq!(folded.transitions, 3); // initialization plus two events
154        assert_eq!(folded.effects.sends.len(), 2);
155        assert!(matches!(folded.exit, Some(Exit::Normal)));
156        assert_eq!(behavior.state().state, 3);
157    }
158}