Common/Effect/ActionEffect.rs
1//! # ActionEffect Struct
2//!
3//! Defines the core `ActionEffect` struct, which is the fundamental unit of
4//! computation in the application's declarative, effects-based architecture.
5
6use std::{future::Future, pin::Pin, sync::Arc};
7
8/// An `ActionEffect` encapsulates an asynchronous operation as a first-class
9/// value.
10///
11/// It is a data structure that contains a function. This function, when
12/// provided with its required capability (`TCapability`), produces a `Future`
13/// that will yield the result of the operation. This pattern cleanly separates
14/// the *definition* of an operation from its *execution*.
15///
16/// # Type Parameters
17///
18/// * `TCapability`: The type of the capability (e.g., `Arc<dyn
19/// FileSystemReader>`) that the effect's closure requires to run.
20/// * `TError`: The error type that the effect's operation can return.
21/// * `TOutput`: The success output type of the effect's operation.
22pub struct ActionEffect<TCapability, TError, TOutput> {
23 /// The wrapped asynchronous function. It is stored in an `Arc` to make the
24 /// `ActionEffect` struct itself cheap to clone.
25 pub Function:
26 Arc<dyn Fn(TCapability) -> Pin<Box<dyn Future<Output = Result<TOutput, TError>> + Send>> + Send + Sync>,
27}
28
29impl<TCapability, TError, TOutput> ActionEffect<TCapability, TError, TOutput> {
30 /// Creates a new `ActionEffect` from a given function closure.
31 pub fn New(
32 Function:Arc<
33 dyn Fn(TCapability) -> Pin<Box<dyn Future<Output = Result<TOutput, TError>> + Send>> + Send + Sync,
34 >,
35 ) -> Self {
36 Self { Function }
37 }
38
39 /// Applies the effect by executing its wrapped function with the provided
40 /// capability. This is typically called by an `ApplicationRunTime`.
41 pub async fn Apply(&self, Capability:TCapability) -> Result<TOutput, TError>
42 where
43 TCapability: Clone, {
44 (self.Function)(Capability).await
45 }
46
47 /// Transforms the output of an effect from `TOutput` to `TNewOutput`.
48 pub fn map<TNewOutput, F>(self, _Function:F) -> ActionEffect<TCapability, TError, TNewOutput>
49 where
50 TCapability: 'static + Send,
51 TError: 'static,
52 TOutput: 'static + Send,
53 TNewOutput: 'static,
54 F: Fn(TOutput) -> TNewOutput + Send + Sync + 'static + Copy, {
55 ActionEffect::New(Arc::new(move |Capability| {
56 let Function = self.Function.clone();
57
58 Box::pin(async move {
59 let Result = (Function)(Capability).await?;
60
61 Ok(_Function(Result))
62 })
63 }))
64 }
65}
66
67impl<TCapability, TError, TOutput> Clone for ActionEffect<TCapability, TError, TOutput> {
68 /// Clones the `ActionEffect`. This is a cheap operation as it only clones
69 /// the `Arc` pointer to the underlying function.
70 fn clone(&self) -> Self { ActionEffect { Function:Arc::clone(&self.Function) } }
71}