Skip to main content

AsyncM

Struct AsyncM 

Source
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>

Source

pub fn new<G, F>(f: G) -> Self
where G: Fn() -> F + Send + Sync + 'static, F: Future<Output = A> + Send + 'static,

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 futures
  • F - 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);
}
Source

pub fn pure(value: A) -> Self
where A: Clone + Send + Sync + 'static,

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");
}
Source

pub async fn try_get(&self) -> A
where 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);
}
Source

pub fn fmap<B, F, Fut>(&self, f: F) -> AsyncM<B>
where B: Send + 'static, F: Fn(A) -> Fut + Send + Sync + Clone + 'static, Fut: Future<Output = B> + Send + 'static, A: Clone,

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 transforms A into B
§Type Parameters
  • B - The type of the result after applying the function
  • F - The type of the function
  • Fut - 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");
}
Source

pub fn bind<B, F, Fut>(&self, f: F) -> AsyncM<B>
where B: Send + Sync + Clone + 'static, F: Fn(A) -> Fut + Send + Sync + Clone + 'static, Fut: Future<Output = AsyncM<B>> + Send + 'static, A: Clone,

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 function
  • F - The type of the function
  • Fut - 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);
}
Source

pub fn apply<B, F>(&self, mf: AsyncM<F>) -> AsyncM<B>
where B: Send + Sync + Clone + 'static, F: Fn(A) -> B + Clone + Send + Sync + 'static, A: Clone,

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);
}
Source

pub fn from_result_or_default<F, Fut, E>(f: F, default_value: A) -> AsyncM<A>
where F: Fn() -> Fut + Send + Sync + Clone + 'static, Fut: Future<Output = Result<A, E>> + Send + 'static, E: Send + Sync + 'static, A: Clone + Send + Sync + 'static,

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 Result
  • default_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);
}
Source

pub fn fmap_owned<B, F, Fut>(self, f: F) -> AsyncM<B>
where F: Fn(A) -> Fut + Clone + Send + Sync + 'static, Fut: Future<Output = B> + Send + 'static, B: Send + 'static, A: Clone,

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 transforms A into B
§Type Parameters
  • B - The type of the result after applying the function
  • F - The type of the function
  • Fut - 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);
}
Source

pub fn bind_owned<B, F, Fut>(self, f: F) -> AsyncM<B>
where F: Fn(A) -> Fut + Clone + Send + Sync + 'static, Fut: Future<Output = AsyncM<B>> + Send + 'static, B: Send + Sync + Clone + 'static, A: Clone,

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 function
  • F - The type of the function
  • Fut - 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);
}
Source

pub fn apply_owned<B, F>(self, mf: AsyncM<F>) -> AsyncM<B>
where F: Fn(A) -> B + Clone + Send + Sync + 'static, B: Send + Sync + 'static, A: Clone,

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 function
  • F - 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);
}
Source

pub fn zip_with<B, C, F>(self, other: AsyncM<B>, f: F) -> AsyncM<C>
where F: Fn(A, B) -> C + Send + Sync + Clone + 'static, B: Send + Sync + Clone + 'static, C: Send + Sync + Clone + 'static, A: Clone,

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 parallel
  • f - 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");
}
Source

pub fn zip<B>(self, other: AsyncM<B>) -> AsyncM<(A, B)>
where B: Send + Sync + Clone + 'static, A: Clone,

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");
}
Source

pub fn recover_with(self, default: A) -> AsyncM<A>
where A: Send + Sync + Clone,

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);
}

Trait Implementations§

Source§

impl<A: Arbitrary + 'static + Send + Sync> Arbitrary for AsyncM<A>

Source§

fn arbitrary(g: &mut Gen) -> Self

Return an arbitrary value. Read more
Source§

fn shrink(&self) -> Box<dyn Iterator<Item = Self>>

Return an iterator of values that are smaller than itself. Read more
Source§

impl<A: Clone> Clone for AsyncM<A>

Source§

fn clone(&self) -> AsyncM<A>

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more

Auto Trait Implementations§

§

impl<A> !RefUnwindSafe for AsyncM<A>

§

impl<A> !UnwindSafe for AsyncM<A>

§

impl<A> Freeze for AsyncM<A>

§

impl<A> Send for AsyncM<A>
where A: Send + Sync,

§

impl<A> Sync for AsyncM<A>
where A: Sync + Send,

§

impl<A> Unpin for AsyncM<A>
where A: Unpin,

§

impl<A> UnsafeUnpin for AsyncM<A>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> PureExt for T
where T: Clone,

Source§

fn to_pure<P>(&self) -> P::Output<Self>
where P: Pure, Self: Clone,

Lift a value into a context. Read more
Source§

fn to_pure_owned<P>(self) -> P::Output<Self>
where P: Pure, Self: Clone,

Lift a value into a context, consuming the value. Read more
Source§

fn pair_with<P, U>(&self, other: &U) -> P::Output<(Self, U)>
where P: Pure, Self: Clone, U: Clone,

Lift a pair of values into a context. Read more
Source§

fn lift_other<P, U>(&self, other: &U) -> P::Output<U>
where P: Pure, U: Clone,

Lift another value into a context. Read more
Source§

fn combine_with<P, U, V>( &self, other: &U, f: impl Fn(&Self, &U) -> V, ) -> P::Output<V>
where P: Pure, Self: Clone, U: Clone, V: Clone,

Combine two values into a new value and lift it into a context. Read more
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.