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}