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