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