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, BehaviorAddr, delegate_transition, initialize};
6use crate::actor::{Address, BirthMode, Create};
7use crate::effects::{Actions, SendEffects};
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: SendEffects, 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: SendEffects, 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<BehaviorAddr<B>, B::Sends, <B::Birth as BirthMode>::Child>,
106    FoldFailure<BehaviorAddr<B>, 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::Protocol for Accumulator {
182        type Addr = MailAddr;
183        type Msg = u8;
184    }
185
186    impl Behavior for Accumulator {
187        type Protocol = Self;
188        type Event = User<MailAddr, u8>;
189        type Sends = Vec<Delivery<Sink>>;
190        type Ph = Never;
191        type Error = Never;
192        type Birth = NoBirths;
193
194        fn transition(
195            &mut self,
196            _: crate::ActiveTurn,
197            event: Self::Event,
198        ) -> crate::BehaviorActed<Self> {
199            self.sum += event.message;
200            let sends = vec![Delivery::new(Recipient::global(MailAddr(9)), self.sum)];
201            Ok(Actions::new(
202                sends,
203                Vec::new(),
204                if self.sum >= self.stop_at {
205                    Step::Stop(Stopped)
206                } else {
207                    Step::Continue
208                },
209            ))
210        }
211    }
212
213    impl behavior::Protocol for Sink {
214        type Addr = MailAddr;
215        type Msg = u8;
216    }
217
218    impl Behavior for Sink {
219        type Protocol = Self;
220        type Event = User<MailAddr, u8>;
221        type Sends = Vec<Never>;
222        type Ph = Never;
223        type Error = Never;
224        type Birth = NoBirths;
225
226        fn init(&mut self, _: crate::InitializationTurn) -> crate::BehaviorActed<Self> {
227            Ok(Actions::cont())
228        }
229
230        fn transition(
231            &mut self,
232            _: crate::ActiveTurn,
233            _: Self::Event,
234        ) -> crate::BehaviorActed<Self> {
235            Ok(Actions::cont())
236        }
237    }
238
239    #[test]
240    fn event_fold_short_circuits_and_accepts_capturing_transitions() {
241        let behavior = Accumulator { sum: 0, stop_at: 3 };
242
243        let folded = fold_events(
244            behavior,
245            [
246                User::new(MailAddr(1), 1),
247                User::new(MailAddr(1), 2),
248                User::new(MailAddr(1), 100),
249            ],
250        )
251        .unwrap();
252
253        assert_eq!(folded.transitions, 3); // initialization plus two events
254        assert_eq!(folded.effects.sends.len(), 2);
255        assert!(folded.stopped);
256    }
257}