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    impl Behavior for Accumulator {
165        type Addr = MailAddr;
166        type Msg = u8;
167        type Event = User<MailAddr, u8>;
168        type Sends = Vec<Delivery<Sink>>;
169        type Ph = Never;
170        type Error = Never;
171        type Birth = NoBirths;
172
173        fn transition(
174            &mut self,
175            _: crate::ActiveTurn,
176            event: Self::Event,
177        ) -> crate::BehaviorActed<Self> {
178            self.sum += event.message;
179            let sends = vec![Delivery::new(Recipient::global(MailAddr(9)), self.sum)];
180            Ok(Actions::new(
181                sends,
182                Vec::new(),
183                if self.sum >= self.stop_at {
184                    Step::Stop(Stopped)
185                } else {
186                    Step::Continue
187                },
188            ))
189        }
190    }
191
192    impl Behavior for Sink {
193        type Addr = MailAddr;
194        type Msg = u8;
195        type Event = User<MailAddr, u8>;
196        type Sends = Vec<Never>;
197        type Ph = Never;
198        type Error = Never;
199        type Birth = NoBirths;
200
201        fn init(&mut self, _: crate::InitializationTurn) -> crate::BehaviorActed<Self> {
202            Ok(Actions::cont())
203        }
204
205        fn transition(
206            &mut self,
207            _: crate::ActiveTurn,
208            _: Self::Event,
209        ) -> crate::BehaviorActed<Self> {
210            Ok(Actions::cont())
211        }
212    }
213
214    #[test]
215    fn event_fold_short_circuits_and_accepts_capturing_transitions() {
216        let behavior = Accumulator { sum: 0, stop_at: 3 };
217
218        let folded = fold_events(
219            behavior,
220            [
221                User::new(MailAddr(1), 1),
222                User::new(MailAddr(1), 2),
223                User::new(MailAddr(1), 100),
224            ],
225        )
226        .unwrap();
227
228        assert_eq!(folded.transitions, 3); // initialization plus two events
229        assert_eq!(folded.effects.sends.len(), 2);
230        assert!(folded.stopped);
231    }
232}