Skip to main content

behavior/
reducer.rs

1//! Pure reduction of transition actions and event streams.
2
3use core::ops::ControlFlow;
4
5use super::{Behavior, delegate_transition, initialize};
6use crate::actor::{Address, BirthMode, Create};
7use crate::effects::{Actions, SendAlgebra};
8use crate::next::{Never, Step, Stopped};
9
10/// The accumulated observable effects of a transition prefix.
11pub struct Effects<A: Address, Sends, New> {
12    pub sends: Sends,
13    pub creates: Vec<Create<A, New>>,
14}
15
16/// The result of folding initialization and an event stream.
17pub struct Folded<A: Address, Sends, New> {
18    pub effects: Effects<A, Sends, New>,
19    pub stopped: bool,
20    pub transitions: usize,
21}
22
23/// A controlled fold failure together with every previously committed effect.
24pub struct FoldFailure<A: Address, Sends, New, E> {
25    pub effects: Effects<A, Sends, New>,
26    pub error: E,
27    pub transitions: usize,
28}
29
30impl<A: Address, Sends, New, E: core::fmt::Debug> core::fmt::Debug
31    for FoldFailure<A, Sends, New, E>
32{
33    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
34        formatter
35            .debug_struct("FoldFailure")
36            .field("error", &self.error)
37            .field("transitions", &self.transitions)
38            .finish_non_exhaustive()
39    }
40}
41
42/// A left fold over Bombay actions.
43pub struct ActionReducer<A: Address, Sends, New> {
44    effects: Effects<A, Sends, New>,
45    transitions: usize,
46}
47
48impl<A: Address, Sends: SendAlgebra, New> Default for ActionReducer<A, Sends, New> {
49    fn default() -> Self {
50        Self {
51            effects: Effects {
52                sends: Sends::empty(),
53                creates: Vec::new(),
54            },
55            transitions: 0,
56        }
57    }
58}
59
60impl<A: Address, Sends: SendAlgebra, New> ActionReducer<A, Sends, New> {
61    #[must_use]
62    pub fn new() -> Self {
63        Self::default()
64    }
65
66    /// Append one action value. Order is preserved and the first stop verdict
67    /// short-circuits the surrounding fold.
68    pub fn push<Birth: BirthMode<Child = New>>(
69        &mut self,
70        actions: Actions<A, Never, Sends, Birth>,
71    ) -> ControlFlow<Stopped> {
72        self.transitions += 1;
73        self.effects.sends.append(actions.sends);
74        self.effects.creates.extend(actions.creates);
75        match actions.become_ {
76            Step::Continue => ControlFlow::Continue(()),
77            Step::Goto(never) => match never {},
78            Step::Stop(exit) => ControlFlow::Break(exit),
79        }
80    }
81
82    #[must_use]
83    pub fn finish(self, stopped: bool) -> Folded<A, Sends, New> {
84        Folded {
85            effects: self.effects,
86            stopped,
87            transitions: self.transitions,
88        }
89    }
90}
91
92/// Initialize a behavior and left-fold events until exhaustion, controlled
93/// failure, or the first stop verdict.
94///
95/// # Errors
96/// Returns the first controlled behavior failure.
97#[allow(
98    clippy::type_complexity,
99    reason = "the result exposes every behavior-owned effect and child seat"
100)]
101pub fn fold_events<B>(
102    mut behavior: B,
103    events: impl IntoIterator<Item = B::Event>,
104) -> Result<
105    Folded<B::Addr, B::Sends, <B::Birth as BirthMode>::Child>,
106    FoldFailure<B::Addr, B::Sends, <B::Birth as BirthMode>::Child, B::Error>,
107>
108where
109    B: Behavior<Ph = Never>,
110{
111    let mut reducer = ActionReducer::new();
112    let initialization = match initialize(&mut behavior) {
113        Ok(actions) => actions,
114        Err(error) => {
115            let folded = reducer.finish(false);
116            return Err(FoldFailure {
117                effects: folded.effects,
118                error,
119                transitions: folded.transitions,
120            });
121        }
122    };
123    if let ControlFlow::Break(_stopped) = reducer.push(initialization) {
124        return Ok(reducer.finish(true));
125    }
126
127    let result = events.into_iter().try_fold((), |(), event| {
128        let actions = match delegate_transition(&mut behavior, event) {
129            Ok(actions) => actions,
130            Err(error) => return ControlFlow::Break(Err(error)),
131        };
132        match reducer.push(actions) {
133            ControlFlow::Continue(()) => ControlFlow::Continue(()),
134            ControlFlow::Break(exit) => ControlFlow::Break(Ok(exit)),
135        }
136    });
137
138    match result {
139        ControlFlow::Continue(()) => Ok(reducer.finish(false)),
140        ControlFlow::Break(Ok(_stopped)) => Ok(reducer.finish(true)),
141        ControlFlow::Break(Err(error)) => {
142            let folded = reducer.finish(false);
143            Err(FoldFailure {
144                effects: folded.effects,
145                error,
146                transitions: folded.transitions,
147            })
148        }
149    }
150}
151
152#[cfg(test)]
153mod tests {
154    use super::*;
155    use crate::{Actions, Behavior, Delivery, MailAddr, NoBirths, Recipient, User};
156
157    struct Sink;
158
159    struct Accumulator {
160        sum: u8,
161        stop_at: u8,
162    }
163
164    #[test]
165    fn fold_failure_debug_reports_error_and_transition_count() {
166        let failure = FoldFailure::<MailAddr, Vec<u8>, (), _> {
167            effects: Effects {
168                sends: vec![1],
169                creates: Vec::new(),
170            },
171            error: "boom",
172            transitions: 2,
173        };
174
175        assert_eq!(
176            format!("{failure:?}"),
177            "FoldFailure { error: \"boom\", transitions: 2, .. }"
178        );
179    }
180
181    impl Behavior for Accumulator {
182        type Addr = MailAddr;
183        type Msg = u8;
184        type Event = User<MailAddr, u8>;
185        type Sends = Vec<Delivery<Sink>>;
186        type Ph = Never;
187        type Error = Never;
188        type Birth = NoBirths;
189
190        fn transition(
191            &mut self,
192            _: crate::ActiveTurn,
193            event: Self::Event,
194        ) -> crate::BehaviorActed<Self> {
195            self.sum += event.message;
196            let sends = vec![Delivery::new(Recipient::global(MailAddr(9)), self.sum)];
197            Ok(Actions::new(
198                sends,
199                Vec::new(),
200                if self.sum >= self.stop_at {
201                    Step::Stop(Stopped)
202                } else {
203                    Step::Continue
204                },
205            ))
206        }
207    }
208
209    impl Behavior for Sink {
210        type Addr = MailAddr;
211        type Msg = u8;
212        type Event = User<MailAddr, u8>;
213        type Sends = Vec<Never>;
214        type Ph = Never;
215        type Error = Never;
216        type Birth = NoBirths;
217
218        fn init(&mut self, _: crate::InitializationTurn) -> crate::BehaviorActed<Self> {
219            Ok(Actions::cont())
220        }
221
222        fn transition(
223            &mut self,
224            _: crate::ActiveTurn,
225            _: Self::Event,
226        ) -> crate::BehaviorActed<Self> {
227            Ok(Actions::cont())
228        }
229    }
230
231    #[test]
232    fn event_fold_short_circuits_and_accepts_capturing_transitions() {
233        let behavior = Accumulator { sum: 0, stop_at: 3 };
234
235        let folded = fold_events(
236            behavior,
237            [
238                User::new(MailAddr(1), 1),
239                User::new(MailAddr(1), 2),
240                User::new(MailAddr(1), 100),
241            ],
242        )
243        .unwrap();
244
245        assert_eq!(folded.transitions, 3); // initialization plus two events
246        assert_eq!(folded.effects.sends.len(), 2);
247        assert!(folded.stopped);
248    }
249}