Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Helper Combinators

Use composition to describe a small operation clearly. Prefer a named domain struct or ordinary function when a long chain makes the data flow difficult to follow.

Accumulate independent validations

use stillwater::Validation;

let values = Validation::<i32, Vec<&str>>::all((
    Validation::<_, Vec<&str>>::success(1),
    Validation::success(2),
));
assert_eq!(values.into_result(), Ok((1, 2)));

let failures = Validation::<i32, Vec<&str>>::all_vec(vec![
    Validation::failure(vec!["name"]),
    Validation::failure(vec!["email"]),
]);
assert_eq!(failures.into_result(), Err(vec!["name", "email"]));

Map, chain, and translate errors

use stillwater::prelude::*;

tokio_test::block_on(async {
    let effect = pure::<_, &str, ()>(21)
        .map(|value| value * 2)
        .and_then(|value| from_result(Ok(value + 1)))
        .map_err(str::to_string);
    assert_eq!(effect.run(&()).await, Ok(43));
});

map transforms a value; and_then constructs a dependent effect; map_err translates an error at a boundary. None of these automatically accumulates failures.

Write a small reusable wrapper

Accept an effect through its trait instead of using the removed generic Effect struct.

use stillwater::prelude::*;

fn require_positive<E>(effect: E) -> impl Effect<Output = i32, Error = &'static str, Env = E::Env>
where
    E: Effect<Output = i32, Error = &'static str>,
{
    effect.ensure(|value| *value > 0, "must be positive")
}

tokio_test::block_on(async {
    assert_eq!(require_positive(pure::<_, &str, ()>(1)).run(&()).await, Ok(1));
    assert_eq!(require_positive(pure::<_, &str, ()>(0)).run(&()).await, Err("must be positive"));
});

Retry requires a factory because running an effect consumes it. Use the existing retry and timeout helpers rather than a wrapper that pretends to rerun the same effect. See effects for the core architecture.