pub struct AsyncM<A> { /* private fields */ }Expand description
The asynchronous monad, which represents a computation that will eventually produce a value.
AsyncM provides a way to work with asynchronous operations in a functional style,
allowing composition and sequencing of async computations while maintaining
referentially-transparent composition when the provided closures are pure (free of observable
side effects).
§Type Parameters
A- The type of the value that will be produced by the async computation
§Examples
use rustica::datatypes::async_monad::AsyncM;
use tokio;
#[tokio::main]
async fn main() {
// Create an async computation
let computation: AsyncM<i32> = AsyncM::pure(42);
// Run the computation and get the result
let result = computation.try_get().await;
assert_eq!(result, 42);
// Transform the result using fmap
let transformed = computation.fmap(|x| async move { x * 2 });
assert_eq!(transformed.try_get().await, 84);
}
§Type Class Laws
For computations whose provided closures are pure (no observable side effects) and do not
panic, AsyncM satisfies the following laws:
§Functor Laws
// Identity: fmap id = id
let m = AsyncM::pure(42);
let identity = m.clone().fmap(|x| async move { x });
assert_eq!(m.try_get().await, identity.try_get().await);
// Composition: fmap (f . g) = fmap f . fmap g
let f = |x: i32| async move { x * 2 };
let g = |x: i32| async move { x + 1 };
let m = AsyncM::pure(10);
let composed = m.clone().fmap(|x| async move { (x + 1) * 2 });
let chained = m.clone().fmap(g).fmap(f);
assert_eq!(composed.try_get().await, chained.try_get().await);§Applicative Laws
// Identity: pure id <*> v = v
let v = AsyncM::pure(42);
let id_fn = AsyncM::pure(|x: i32| x);
assert_eq!(v.clone().apply(id_fn).try_get().await, v.try_get().await);
// Composition: pure (.) <*> u <*> v <*> w = u <*> (v <*> w)
let add_one = AsyncM::pure(|x: i32| x + 1);
let mul_two = AsyncM::pure(|x: i32| x * 2);
let value = AsyncM::pure(10);
// Left side: compose functions first
let compose = AsyncM::pure(|f: fn(i32) -> i32| {
move |g: fn(i32) -> i32| move |x| f(g(x))
});
// ... (composition example would be complex due to Rust's type system)§Monad Laws
// Left identity: pure a >>= f = f a
let a = 42;
let f = |x| async move { AsyncM::pure(x * 2) };
let left = AsyncM::pure(a).bind(f.clone());
let right = f(a).await;
assert_eq!(left.try_get().await, right.try_get().await);
// Right identity: m >>= pure = m
let m = AsyncM::pure(42);
let bound = m.clone().bind(|x| async move { AsyncM::pure(x) });
assert_eq!(m.try_get().await, bound.try_get().await);
// Associativity: (m >>= f) >>= g = m >>= (\x -> f x >>= g)
let m = AsyncM::pure(10);
let f = |x| async move { AsyncM::pure(x + 1) };
let g = |x| async move { AsyncM::pure(x * 2) };
let left = m.clone().bind(f.clone()).bind(g.clone());
let right = m.bind(move |x| async move {
f(x).await.bind(g.clone())
});
assert_eq!(left.try_get().await, right.try_get().await);§Advanced Examples
§Error Handling with AsyncM
// Chaining fallible operations
async fn fetch_user_id() -> Result<i32, String> {
Ok(42)
}
async fn fetch_user_name(id: i32) -> Result<String, String> {
Ok(format!("User{}", id))
}
let user_info = AsyncM::from_result_or_default(
|| fetch_user_id(),
0
).bind(|id| async move {
if id == 0 {
AsyncM::pure("Anonymous".to_string())
} else {
AsyncM::from_result_or_default(
move || fetch_user_name(id),
"Unknown".to_string()
)
}
});
println!("User: {}", user_info.try_get().await);§Parallel Computation Patterns
// Parallel API calls
let fetch_weather = AsyncM::new(|| async {
tokio::time::sleep(Duration::from_millis(100)).await;
"Sunny, 22°C"
});
let fetch_news = AsyncM::new(|| async {
tokio::time::sleep(Duration::from_millis(150)).await;
vec!["Breaking: Rust 2.0 released!", "Tech: AsyncM patterns"]
});
let fetch_stocks = AsyncM::new(|| async {
tokio::time::sleep(Duration::from_millis(80)).await;
vec![("AAPL", 150.0), ("GOOGL", 2800.0)]
});
// Combine all results in parallel
let start = Instant::now();
let dashboard = fetch_weather
.zip(fetch_news)
.zip(fetch_stocks)
.fmap(|((weather, news), stocks)| async move {
format!(
"Weather: {}\nTop News: {}\nStocks: {:?}",
weather, news[0], stocks[0]
)
});
println!("{}", dashboard.try_get().await);
println!("Total time: {:?} (parallel execution)", start.elapsed());§Resource Management Pattern
// Safely manage shared resources
#[derive(Clone)]
struct Database {
connections: Arc<Mutex<Vec<String>>>,
}
impl Database {
fn query(&self, sql: &str) -> AsyncM<String> {
let connections = self.connections.clone();
let sql = sql.to_string();
AsyncM::new(move || {
let connections = connections.clone();
let sql = sql.clone();
async move {
let mut conns = connections.lock().unwrap();
conns.push(format!("Executed: {}", sql));
format!("Result for: {}", sql)
}
})
}
}
let db = Database {
connections: Arc::new(Mutex::new(Vec::new())),
};
// Chain multiple queries
let result = db.query("SELECT * FROM users")
.bind(move |users| {
let db = db.clone();
async move {
db.query(&format!("SELECT orders FROM orders WHERE user IN ({})", users))
}
});
println!("Query result: {}", result.try_get().await);Implementations§
Source§impl<A: Send + Sync + 'static> AsyncM<A>
impl<A: Send + Sync + 'static> AsyncM<A>
Sourcepub fn new<G, F>(f: G) -> Self
pub fn new<G, F>(f: G) -> Self
Creates a new async computation from a future-producing function.
This constructor allows you to create an AsyncM from any function that
produces a Future when called.
§Arguments
f- A function that creates a new future each time it’s called
§Type Parameters
G- The type of the function that produces futuresF- The type of the future produced by the function
§Examples
use rustica::datatypes::async_monad::AsyncM;
use tokio;
use std::time::Duration;
#[tokio::main]
async fn main() {
// Create an async computation that produces a value after a delay
let delayed = AsyncM::new(|| async {
tokio::time::sleep(Duration::from_millis(10)).await;
42
});
assert_eq!(delayed.try_get().await, 42);
}Sourcepub fn pure(value: A) -> Self
pub fn pure(value: A) -> Self
Creates a pure async computation that just returns the given value.
This operation lifts a pure value into the AsyncM context without any
asynchronous computation, following the pure value lifting pattern.
§Arguments
value- The value to wrap in an async computation
§Examples
use rustica::datatypes::async_monad::AsyncM;
use tokio;
#[tokio::main]
async fn main() {
// Create a pure async value
let async_int: AsyncM<i32> = AsyncM::pure(42);
assert_eq!(async_int.try_get().await, 42);
// Works with any type that implements Send
let async_string: AsyncM<String> = AsyncM::pure("hello".to_string());
assert_eq!(async_string.try_get().await, "hello");
}Sourcepub async fn try_get(&self) -> Awhere
A: Clone,
pub async fn try_get(&self) -> Awhere
A: Clone,
Executes this async computation and returns its value.
This method runs the async computation and waits for it to complete.
Note that this method does not return a Result: it will propagate panics from the underlying
future. To convert panics into a default value, use AsyncM::recover_with.
If this AsyncM was created with AsyncM::new, calling try_get multiple times will run the
underlying computation multiple times.
§Returns
The computed value of type A
§Examples
use rustica::datatypes::async_monad::AsyncM;
use tokio;
#[tokio::main]
async fn main() {
let computation = AsyncM::pure(42);
// Run the computation and get the result
let result = computation.try_get().await;
assert_eq!(result, 42);
}Sourcepub fn fmap<B, F, Fut>(&self, f: F) -> AsyncM<B>
pub fn fmap<B, F, Fut>(&self, f: F) -> AsyncM<B>
Maps a function over the result of this async computation.
This operation allows transformation of the value inside the AsyncM context
while preserving the asynchronous computation structure.
§Arguments
f- An async function that transformsAintoB
§Type Parameters
B- The type of the result after applying the functionF- The type of the functionFut- The type of the future returned by the function
§Examples
use rustica::datatypes::async_monad::AsyncM;
use tokio;
#[tokio::main]
async fn main() {
let computation = AsyncM::pure(42);
// Map a function over the async value
let doubled = computation.clone().fmap(|x| async move { x * 2 });
assert_eq!(doubled.try_get().await, 84);
// Chain multiple transformations
let result = computation
.fmap(|x| async move { x + 10 })
.fmap(|x| async move { x.to_string() });
assert_eq!(result.try_get().await, "52");
}Sourcepub fn bind<B, F, Fut>(&self, f: F) -> AsyncM<B>
pub fn bind<B, F, Fut>(&self, f: F) -> AsyncM<B>
Chains this computation with another async computation.
This is a fundamental sequencing operation that allows async operations to depend on the results of previous operations.
§Arguments
f- An async function that takes the result of this computation and returns a new computation
§Type Parameters
B- The type of the result after applying the functionF- The type of the functionFut- The type of the future returned by the function
§Examples
use rustica::datatypes::async_monad::AsyncM;
use tokio;
#[tokio::main]
async fn main() {
let computation = AsyncM::pure(42);
// Chain with another async computation
let result = computation.clone().bind(|x| async move {
// This function returns a new AsyncM
AsyncM::pure(x + 10)
});
assert_eq!(result.try_get().await, 52);
// Chain multiple bind operations
let result = computation
.bind(|x| async move { AsyncM::pure(x + 10) })
.bind(|x| async move { AsyncM::pure(x * 2) });
assert_eq!(result.try_get().await, 104);
}Sourcepub fn apply<B, F>(&self, mf: AsyncM<F>) -> AsyncM<B>
pub fn apply<B, F>(&self, mf: AsyncM<F>) -> AsyncM<B>
Applies a wrapped function to this async computation.
This operation allows application of a function wrapped in AsyncM to a value wrapped in AsyncM,
following the applicative pattern.
§Arguments
mf- An async computation that produces a function
§Type Parameters
B- The type of the result after applying the function
§Examples
use rustica::datatypes::async_monad::AsyncM;
use tokio;
#[tokio::main]
async fn main() {
let computation = AsyncM::pure(42);
// Create a function wrapped in AsyncM
let func = AsyncM::pure(|x: i32| x * 2);
// Apply the wrapped function to the wrapped value
let result = computation.apply(func);
assert_eq!(result.try_get().await, 84);
}Sourcepub fn from_result_or_default<F, Fut, E>(f: F, default_value: A) -> AsyncM<A>
pub fn from_result_or_default<F, Fut, E>(f: F, default_value: A) -> AsyncM<A>
Executes an async Result and maps errors to a default value.
This is a lazy operation: the provided function f is invoked each time you call
AsyncM::try_get.
§Arguments
f- A function that produces a future that returns a Resultdefault_value- The value to return if the Result is an Err
§Returns
An AsyncM that yields the Ok value, or yields default_value if f() returns Err.
The error value is discarded.
§Examples
use rustica::datatypes::async_monad::AsyncM;
use tokio;
#[tokio::main]
async fn main() {
// A function that returns a Result in a Future
async fn divide(a: i32, b: i32) -> Result<i32, &'static str> {
if b == 0 {
Err("Cannot divide by zero")
} else {
Ok(a / b)
}
}
// Handle a successful result
let success = AsyncM::from_result_or_default(|| divide(10, 2), 0);
assert_eq!(success.try_get().await, 5);
// Handle an error with default value
let failure = AsyncM::from_result_or_default(|| divide(10, 0), 0);
assert_eq!(failure.try_get().await, 0);
}Sourcepub fn fmap_owned<B, F, Fut>(self, f: F) -> AsyncM<B>
pub fn fmap_owned<B, F, Fut>(self, f: F) -> AsyncM<B>
Maps a function over the result of this async computation, consuming the original.
This is an ownership-aware version of fmap that avoids unnecessary cloning
by taking ownership of self.
§Arguments
f- An async function that transformsAintoB
§Type Parameters
B- The type of the result after applying the functionF- The type of the functionFut- The type of the future returned by the function
§Examples
use rustica::datatypes::async_monad::AsyncM;
use tokio;
#[tokio::main]
async fn main() {
// Create an AsyncM and consume it with map_owned
let result = AsyncM::pure(42)
.fmap_owned(|x| async move { x * 2 });
assert_eq!(result.try_get().await, 84);
}Sourcepub fn bind_owned<B, F, Fut>(self, f: F) -> AsyncM<B>
pub fn bind_owned<B, F, Fut>(self, f: F) -> AsyncM<B>
Chains this computation with another async computation, consuming the original.
This is an ownership-aware version of bind that avoids unnecessary cloning
by taking ownership of self.
§Arguments
f- An async function that takes the result of this computation and returns a new computation
§Type Parameters
B- The type of the result after applying the functionF- The type of the functionFut- The type of the future returned by the function
§Examples
use rustica::datatypes::async_monad::AsyncM;
use tokio;
#[tokio::main]
async fn main() {
// Create an AsyncM and consume it with bind_owned
let result = AsyncM::pure(42)
.bind_owned(|x| async move {
// This function returns a new AsyncM
AsyncM::pure(x + 10)
});
assert_eq!(result.try_get().await, 52);
}Sourcepub fn apply_owned<B, F>(self, mf: AsyncM<F>) -> AsyncM<B>
pub fn apply_owned<B, F>(self, mf: AsyncM<F>) -> AsyncM<B>
Applies a wrapped function to this async computation, consuming both.
This is an ownership-aware version of apply that avoids unnecessary cloning
by taking ownership of both self and the function.
§Arguments
mf- An async computation that produces a function
§Type Parameters
B- The type of the result after applying the functionF- The type of the function
§Examples
use rustica::datatypes::async_monad::AsyncM;
use tokio;
#[tokio::main]
async fn main() {
let computation = AsyncM::pure(42);
let func = AsyncM::pure(|x: i32| x * 2);
// Apply the function to the value, consuming both
let result = computation.apply_owned(func);
assert_eq!(result.try_get().await, 84);
}Sourcepub fn zip_with<B, C, F>(self, other: AsyncM<B>, f: F) -> AsyncM<C>
pub fn zip_with<B, C, F>(self, other: AsyncM<B>, f: F) -> AsyncM<C>
Runs multiple AsyncM operations in parallel and combines their results.
This function allows you to run two AsyncM operations concurrently and then combine their results using a provided function.
§Arguments
other- Another AsyncM operation to run in parallelf- A function that combines the results of both operations
§Examples
use rustica::datatypes::async_monad::AsyncM;
use tokio;
use std::time::Duration;
#[tokio::main]
async fn main() {
// Create two operations that take some time
let op1 = AsyncM::new(|| async {
tokio::time::sleep(Duration::from_millis(10)).await;
42
});
let op2 = AsyncM::new(|| async {
tokio::time::sleep(Duration::from_millis(10)).await;
"hello"
});
// Run them in parallel and combine results
let result = op1.zip_with(op2, |a, b| format!("{} {}", b, a));
assert_eq!(result.try_get().await, "hello 42");
}Sourcepub fn zip<B>(self, other: AsyncM<B>) -> AsyncM<(A, B)>
pub fn zip<B>(self, other: AsyncM<B>) -> AsyncM<(A, B)>
Zips this AsyncM with another AsyncM, returning a tuple of their results.
This is a convenience method for zip_with that simply returns the pair.
§Examples
use rustica::datatypes::async_monad::AsyncM;
use tokio;
#[tokio::main]
async fn main() {
let a = AsyncM::pure(42);
let b = AsyncM::pure("hello");
let pair = a.zip(b);
let (num, str) = pair.try_get().await;
assert_eq!(num, 42);
assert_eq!(str, "hello");
}Sourcepub fn recover_with(self, default: A) -> AsyncM<A>
pub fn recover_with(self, default: A) -> AsyncM<A>
Recovers from panics in the computation with a default value.
This method attempts to run the async computation and, if it panics, returns the provided default value instead.
This only handles unwind panics (via catch_unwind). It does not turn Result::Err into
a default value; use AsyncM::from_result_or_default for that.
§Arguments
default- The default value to return if the computation panics
§Examples
use rustica::datatypes::async_monad::AsyncM;
use tokio;
#[tokio::main]
async fn main() {
// A computation that will panic
let faulty = AsyncM::new(|| async {
panic!("This will fail!");
#[allow(unreachable_code)]
42
});
// Recover from the panic with a default value
let result = faulty.recover_with(0).try_get().await;
assert_eq!(result, 0);
// A working computation
let working = AsyncM::pure(42);
let result = working.recover_with(0).try_get().await;
assert_eq!(result, 42);
}