computation_process/
algorithm.rs

1use crate::generatable::Generatable;
2use crate::{Collector, Computable, DynAlgorithm, DynGenAlgorithm};
3use cancel_this::Cancellable;
4
5/// A shared interface of objects that provide access to
6/// an immutable `CONTEXT` and mutable `STATE`.
7pub trait Stateful<CONTEXT, STATE> {
8    /// Create new [`Stateful`] instance using values that can be
9    /// converted to `CONTEXT` and `STATE`.
10    fn configure<I1: Into<CONTEXT>, I2: Into<STATE>>(context: I1, initial_state: I2) -> Self
11    where
12        Self: Sized + 'static,
13    {
14        Self::from_parts(context.into(), initial_state.into())
15    }
16
17    /// Create new [`Stateful`] instance from `CONTEXT` and `STATE`.
18    fn from_parts(context: CONTEXT, state: STATE) -> Self
19    where
20        Self: Sized + 'static;
21
22    /// Destruct the [`Stateful`] instance into `CONTEXT` and `STATE` objects.
23    fn into_parts(self) -> (CONTEXT, STATE);
24
25    /// Access to the underlying immutable `CONTEXT`.
26    fn context(&self) -> &CONTEXT;
27
28    /// Access to the underlying `STATE`.
29    fn state(&self) -> &STATE;
30
31    /// Access to the underlying `STATE` as a mutable reference.
32    ///
33    /// Keep in mind that having a consistent state is important for the correctness
34    /// of [`Algorithm`] and [`GenAlgorithm`]. You should modify the internal state
35    /// of a [`Stateful`] object only in rare, well-defined situations.
36    fn state_mut(&mut self) -> &mut STATE;
37}
38
39/// Extends [`Computable`] trait with immutable `CONTEXT` and mutable `STATE`.
40pub trait Algorithm<CONTEXT, STATE, OUTPUT>: Computable<OUTPUT> + Stateful<CONTEXT, STATE> {
41    /// Configure and immediately execute the computation, skipping over all suspended states.
42    fn run<I1: Into<CONTEXT>, I2: Into<STATE>>(
43        context: I1,
44        initial_state: I2,
45    ) -> Cancellable<OUTPUT>
46    where
47        Self: Sized + 'static,
48    {
49        Self::from_parts(context.into(), initial_state.into()).compute()
50    }
51
52    /// Convert to a dynamic [`Algorithm`] variant.
53    fn dyn_algorithm(self) -> DynAlgorithm<CONTEXT, STATE, OUTPUT>
54    where
55        Self: Sized + 'static,
56    {
57        Box::new(self)
58    }
59}
60
61/// Extends [`Generatable`] trait with immutable `CONTEXT` and mutable `STATE`.
62pub trait GenAlgorithm<CONTEXT, STATE, OUTPUT>:
63    Generatable<OUTPUT> + Stateful<CONTEXT, STATE>
64{
65    /// Convert a [`GenAlgorithm`] into a [`Computable`] object that collects all values
66    /// into a `COLLECTION`.
67    fn computation<COLLECTION: Default + Extend<OUTPUT> + 'static>(
68        self,
69    ) -> impl Computable<COLLECTION>
70    where
71        Self: Sized + 'static,
72    {
73        Collector::<OUTPUT, COLLECTION, Self>::new(self)
74    }
75
76    /// Convert to a dynamic [`GenAlgorithm`] variant.
77    fn dyn_algorithm(self) -> DynGenAlgorithm<CONTEXT, STATE, OUTPUT>
78    where
79        Self: Sized + 'static,
80    {
81        Box::new(self)
82    }
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88    use crate::{Computation, ComputationStep, Generator, GeneratorStep, Incomplete};
89
90    struct TestComputationStep;
91
92    impl ComputationStep<i32, u32, String> for TestComputationStep {
93        fn step(context: &i32, state: &mut u32) -> crate::Completable<String> {
94            *state += 1;
95            if *state < 2 {
96                Err(Incomplete::Suspended)
97            } else {
98                Ok(format!("done-{}", context))
99            }
100        }
101    }
102
103    #[test]
104    fn test_stateful_from_parts() {
105        let stateful = Computation::<i32, u32, String, TestComputationStep>::from_parts(42, 0);
106        assert_eq!(*stateful.context(), 42);
107        assert_eq!(*stateful.state(), 0);
108    }
109
110    #[test]
111    fn test_stateful_configure() {
112        let stateful = Computation::<i32, u32, String, TestComputationStep>::configure(100, 5u32);
113        assert_eq!(*stateful.context(), 100);
114        assert_eq!(*stateful.state(), 5);
115    }
116
117    #[test]
118    fn test_stateful_into_parts() {
119        let stateful = Computation::<i32, u32, String, TestComputationStep>::from_parts(50, 10);
120        let (context, state) = stateful.into_parts();
121        assert_eq!(context, 50);
122        assert_eq!(state, 10);
123    }
124
125    #[test]
126    fn test_stateful_context() {
127        let stateful = Computation::<i32, u32, String, TestComputationStep>::from_parts(200, 0);
128        assert_eq!(*stateful.context(), 200);
129    }
130
131    #[test]
132    fn test_stateful_state() {
133        let stateful = Computation::<i32, u32, String, TestComputationStep>::from_parts(0, 42);
134        assert_eq!(*stateful.state(), 42);
135    }
136
137    #[test]
138    fn test_stateful_state_mut() {
139        let mut stateful = Computation::<i32, u32, String, TestComputationStep>::from_parts(0, 0);
140        *stateful.state_mut() = 100;
141        assert_eq!(*stateful.state(), 100);
142    }
143
144    #[test]
145    fn test_algorithm_run() {
146        let result = Computation::<i32, u32, String, TestComputationStep>::run(42, 0u32).unwrap();
147        assert_eq!(result, "done-42");
148    }
149
150    #[test]
151    fn test_algorithm_dyn_algorithm() {
152        let algorithm = Computation::<i32, u32, String, TestComputationStep>::from_parts(100, 0);
153        let mut dyn_algorithm = algorithm.dyn_algorithm();
154        let result = dyn_algorithm.compute().unwrap();
155        assert_eq!(result, "done-100");
156    }
157
158    struct TestGeneratorStep;
159
160    impl GeneratorStep<i32, u32, String> for TestGeneratorStep {
161        fn step(context: &i32, state: &mut u32) -> crate::Completable<Option<String>> {
162            *state += 1;
163            if *state <= 2 {
164                Ok(Some(format!("{}-{}", context, state)))
165            } else {
166                Ok(None)
167            }
168        }
169    }
170
171    #[test]
172    fn test_gen_algorithm_computation() {
173        let generator = Generator::<i32, u32, String, TestGeneratorStep>::from_parts(42, 0);
174        let mut computation = generator.computation::<Vec<String>>();
175        let result = computation.compute().unwrap();
176        assert_eq!(result, vec!["42-1", "42-2"]);
177    }
178
179    #[test]
180    fn test_gen_algorithm_computation_hashset() {
181        let generator = Generator::<i32, u32, String, TestGeneratorStep>::from_parts(42, 0);
182        let mut computation = generator.computation::<std::collections::HashSet<String>>();
183        let result = computation.compute().unwrap();
184        assert_eq!(result.len(), 2);
185        assert!(result.contains("42-1"));
186        assert!(result.contains("42-2"));
187    }
188
189    #[test]
190    fn test_gen_algorithm_dyn_algorithm() {
191        let generator = Generator::<i32, u32, String, TestGeneratorStep>::from_parts(100, 0);
192        let mut dyn_algorithm = generator.dyn_algorithm();
193        let item = dyn_algorithm.try_next().unwrap().unwrap();
194        assert_eq!(item, "100-1");
195    }
196
197    #[test]
198    fn test_stateful_configure_with_conversions() {
199        // Test that configure works with Into conversions
200        let stateful =
201            Computation::<i32, u32, String, TestComputationStep>::configure(42i16, 10u16);
202        assert_eq!(*stateful.context(), 42i32);
203        assert_eq!(*stateful.state(), 10u32);
204    }
205}