assemble_core/
lazy_evaluation.rs

1//! Lazy evaluation allows for a more simple approach to sharing data between tasks.
2//!
3//! The main driving trait that allows for this is [`Provider`](Provider). Objects that
4//! implement this trait can try to provide a value of that type. There are three main flavors
5//! of providers.
6//!
7//! Properties
8//! ---
9//!
10//! `Prop<T>` - A property that can be set to a specific value.
11//!
12//! ## Usage
13//! Should be used when single values are needed, and this property is needed to either be used
14//! as an output that's set by the task, or the task needs an input. When used with the
15//! [`CreateTask`](crate::tasks::CreateTask) derive macro.
16//! ```
17//! # use assemble_core::lazy_evaluation::Prop;
18//! struct LogTask {
19//!     msg: Prop<String>
20//! }
21//! ```
22//!
23//!
24//!
25//!
26
27pub mod anonymous;
28pub mod prop;
29pub mod providers;
30
31use crate::lazy_evaluation::providers::{FlatMap, Flatten, Map, Zip};
32use crate::Project;
33use crate::__export::{ProjectResult, TaskId};
34use crate::project::buildable::Buildable;
35pub use prop::*;
36use std::collections::HashSet;
37use std::fmt::{Debug, Formatter};
38
39/// The provider trait represents an object that can continuously produce a value. Provider values
40/// can be chained together using the [`ProviderExt`][0] trait.
41///
42/// For convenience, several types from the default library implement this trait.
43/// - `for<T> Option<T> : Provider<T>`
44/// - `for<F, T> F : Provider<T> where F : Fn() -> T`
45///
46/// [0]: ProviderExt
47pub trait Provider<T: Clone + Send + Sync>: Send + Sync + Buildable {
48    /// The missing message for this provider
49    fn missing_message(&self) -> String {
50        String::from("Provider has no value set")
51    }
52    /// Get a value from the provider.
53    ///
54    /// # Panic
55    /// This method will panic if there is no value available.
56    ///
57    /// # Example
58    /// ```
59    /// # use assemble_core::lazy_evaluation::Provider;
60    /// use assemble_core::provider;
61    /// let prop = provider!(|| 10);
62    /// assert_eq!(prop.get(), 10);
63    /// ```
64    fn get(&self) -> T {
65        self.try_get()
66            .unwrap_or_else(|| panic!("{}", self.missing_message()))
67    }
68
69    /// Try to get a value from the provider.
70    ///
71    /// Will return `Some(v)` if value `v` is available, otherwise `None` is returned.
72    ///
73    /// # Example
74    /// ```
75    /// # use assemble_core::lazy_evaluation::Provider;
76    /// use assemble_core::provider;
77    /// let prop = provider!(|| 10);
78    /// assert_eq!(prop.try_get(), Some(10));
79    ///
80    /// // options implement provider
81    /// let prop = Option::<usize>::None;
82    /// assert_eq!(prop.try_get(), None);
83    /// ```
84    fn try_get(&self) -> Option<T>;
85
86    /// Tries to get a value from this provider, returning an error if not available.
87    ///
88    /// The error's message is usually specified by the `missing_message()` method.
89    ///
90    /// # Example
91    /// ```
92    /// # use assemble_core::identifier::Id;
93    /// # use assemble_core::lazy_evaluation::Provider;
94    /// use assemble_core::lazy_evaluation::Prop;
95    /// # use assemble_core::lazy_evaluation::anonymous::AnonymousProvider;
96    /// use assemble_core::provider;
97    ///
98    /// let provider = provider!(|| 3_i32);
99    /// assert!(matches!(provider.fallible_get(), Ok(3)));
100    /// let mut prop = Prop::<i32>::new(Id::from("test"));
101    /// assert!(matches!(prop.fallible_get(), Err(_)));
102    /// prop.set_with(provider).unwrap();
103    /// assert!(matches!(prop.fallible_get(), Ok(3)));
104    /// ```
105    fn fallible_get(&self) -> Result<T, ProviderError> {
106        self.try_get()
107            .ok_or_else(|| ProviderError::new(self.missing_message()))
108    }
109}
110
111assert_obj_safe!(Provider<()>);
112
113/// Provides extensions that are not object safe to the Provider trait.
114pub trait ProviderExt<T: Clone + Send + Sync>: Provider<T> + Sized {
115    /// Creates a provider that can map the output of one provider into some other value.
116    ///
117    /// `transform`: `fn(T) -> R`
118    fn map<R, F>(self, transform: F) -> Map<T, R, F, Self>
119    where
120        R: Send + Sync + Clone,
121        F: Fn(T) -> R + Send + Sync,
122        Self: 'static,
123    {
124        Map::new(self, transform)
125    }
126
127    /// Creates a provider that can map the output of one provider with type `T` into some other value that's
128    /// also a provider of type `R`. The created provider is a provider of type `R`
129    ///
130    /// `transform`: `fn(T) -> impl Provider<R>`
131    fn flat_map<R, P, F>(self, transform: F) -> FlatMap<T, R, Self, P, F>
132    where
133        R: Send + Sync + Clone,
134        P: Provider<R>,
135        F: Fn(T) -> P + Send + Sync,
136    {
137        FlatMap::new(self, transform)
138    }
139
140    /// Flattens a provider that provides another provider
141    fn flatten<B>(self) -> Flatten<T, B, Self>
142    /* FlatMap<T, B, Self, T, fn(T) -> T> */
143    where
144        Self: Clone + 'static,
145        T: Provider<B>,
146        B: Clone + Send + Sync,
147    {
148        self.flat_map(|s| s)
149    }
150
151    /// Creates a provider that can map the output of two provider with type `T1` and `T2` into some other value
152    /// and transforms the two values into one output value of type `B`.
153    ///
154    /// `transform`: `fn(T1, T2) -> B`
155    fn zip<P, B, R, F>(self, other: P, func: F) -> Zip<T, B, R, F>
156    where
157        Self: 'static,
158        P: IntoProvider<B>,
159        <P as IntoProvider<B>>::Provider: 'static,
160        B: Send + Sync + Clone,
161        R: Send + Sync + Clone,
162        F: Fn(T, B) -> R + Send + Sync,
163    {
164        Zip::new(self, other, func)
165    }
166}
167
168impl<P, T> ProviderExt<T> for P
169where
170    T: Clone + Send + Sync,
171    P: Provider<T> + Send + Sync + 'static,
172{
173}
174
175/// Represents that a type can be represented as a provider. All Providers implement this trait.
176pub trait IntoProvider<T: Send + Sync + Clone> {
177    type Provider: Provider<T>;
178
179    fn into_provider(self) -> Self::Provider;
180}
181
182impl<P, T> IntoProvider<T> for P
183where
184    T: Clone + Send + Sync,
185    P: Provider<T> + Send + Sync,
186{
187    type Provider = Self;
188
189    fn into_provider(self) -> Self::Provider {
190        self
191    }
192}
193
194#[derive(Clone)]
195struct Wrapper<T: Clone + Send + Sync>(T);
196
197impl<T: Clone + Send + Sync> Buildable for Wrapper<T> {
198    fn get_dependencies(&self, _: &Project) -> ProjectResult<HashSet<TaskId>> {
199        Ok(HashSet::new())
200    }
201}
202
203impl<T: Clone + Send + Sync> Debug for Wrapper<T> {
204    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
205        f.debug_struct("Wrapper").finish_non_exhaustive()
206    }
207}
208
209impl<T: Clone + Send + Sync> Provider<T> for Wrapper<T> {
210    fn try_get(&self) -> Option<T> {
211        Some(self.0.clone())
212    }
213}
214
215/// A value could not be provided
216#[derive(Debug, thiserror::Error)]
217#[error("{}", message)]
218pub struct ProviderError {
219    message: String,
220}
221
222impl ProviderError {
223    pub fn new(message: String) -> Self {
224        Self { message }
225    }
226}
227
228#[cfg(test)]
229mod tests {
230    use super::*;
231    use crate::lazy_evaluation::anonymous::AnonymousProvider;
232
233    use crate::provider;
234
235    #[test]
236    fn map() {
237        let mut provider = Prop::with_value(5);
238        let mapped = provider.clone().map(|v| v * v);
239        assert_eq!(mapped.get(), 25);
240        provider.set(10).unwrap();
241        assert_eq!(mapped.get(), 100);
242    }
243
244    #[test]
245    fn flat_map() {
246        let mut provider = Prop::with_value(5u32);
247        let flap_map = provider.clone().flat_map(|v| provider!(move || v * v));
248        assert_eq!(flap_map.get(), 25);
249        provider.set(10u32).unwrap();
250        assert_eq!(flap_map.get(), 100);
251    }
252
253    #[test]
254    fn zip() {
255        let mut provider_l = Prop::with_value(5);
256        let mut provider_r = Prop::with_value(6);
257        let zipped = provider_l.clone().zip(provider_r.clone(), |l, r| l * r);
258        assert_eq!(zipped.get(), 30);
259        provider_l.set(10).unwrap();
260        assert_eq!(zipped.get(), 60);
261        provider_r.set(15).iter();
262        assert_eq!(zipped.get(), 150);
263    }
264
265    #[test]
266    fn flatten() {
267        let prop1 = provider!(|| provider!(|| 5));
268        let prop2 = provider!(|| provider!(|| 6));
269        let value = prop1.flatten().zip(prop2.flatten(), |l, r| l * r);
270        assert_eq!(value.get(), 30);
271
272        let _flattend2 = AnonymousProvider::with_value(|| 0).flat_map(|p| provider!(p));
273    }
274}