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

R Revisited — More Than Just a Type Parameter

R is the environment type required to run an effect.

fn get_user(id: u64) -> Effect<User, DbError, Database>

This says: to run get_user, supply a Database environment.

R as a Contract

let effect = get_user(1);

// Missing environment: does not match current runner API.
// run_blocking(effect);

let user = run_blocking(effect, my_database)?;

You can also capture the environment first.

let ready = get_user(1).provide_env(my_database);
let user = run_blocking(ready, ())?;

Composition Requires One Environment Type

Inside one effect! block, bound effects must agree on the same R type after any adaptations.

fn get_user(id: u64) -> Effect<User, DbError, Database> { /* ... */ }
fn get_posts(user_id: u64) -> Effect<Vec<Post>, DbError, Database> { /* ... */ }

fn get_user_with_posts(id: u64) -> Effect<(User, Vec<Post>), DbError, Database> {
    effect! {
        let user = bind* get_user(id);
        let posts = bind* get_posts(user.id);
        (user, posts)
    }
}

When effects need different environment types, adapt them with zoom_env, use a common ServiceContext, or build an HList Context that exposes both services.

fn log(msg: &str) -> Effect<(), LogError, Logger> { /* ... */ }
fn get_user(id: u64) -> Effect<User, DbError, Database> { /* ... */ }

fn get_user_logged(id: u64) -> Effect<User, AppError, AppEnv> {
    effect! {
        bind* log(&format!("Fetching user {id}"))
            .zoom_env(|env: &mut AppEnv| env.logger.clone())
            .map_error(AppError::Log);

        bind* get_user(id)
            .zoom_env(|env: &mut AppEnv| env.database.clone())
            .map_error(AppError::Db)
    }
}

R as Documentation

Effect<Receipt, AppError, ServiceContext> says the computation reads services from a runtime service table. Effect<Receipt, AppError, Context<...>> says exactly which tagged HList cells are required. A concrete environment type like AppEnv documents which aggregate application environment must be supplied.

Why R Instead of Parameters?

Traditional Rust threads dependencies as function parameters. R moves that dependency requirement into the returned effect type, which makes it composable and delayable until the program edge.

The key rule: library functions return honest Effect<A, E, R> values; application edges decide which concrete environment to pass.