When Not to Use the Macro

effect! is the idiomatic choice for most multi-step computations. But it's a macro — which means it has edges. Knowing when to reach for raw flat_map instead saves debugging time.

Use Raw flat_map for Single-Step Transforms

When there's exactly one effectful step and you're transforming its result, flat_map is cleaner:

#![allow(unused)]
fn main() {
// Unnecessarily verbose
effect! {
    let id = ~ parse_id(raw);
    id
}

// Clear and direct
parse_id(raw).flat_map(|id| succeed(id))
// or just:
parse_id(raw)
}

Use effect! when you have two or more sequential steps. For one, flat_map or .map is usually enough.

Use Combinators for Structural Patterns

Some patterns have named combinators that are more expressive than macros:

#![allow(unused)]
fn main() {
// Instead of:
effect! {
    let a = ~ step_a();
    let b = ~ step_b();
    (a, b)
}

// Consider (when steps are independent):
step_a().zip(step_b())
}

zip communicates intent: "I need both, in any order." The effect! version implies sequential dependency. For independent steps, prefer explicit combinators. (For concurrent independent steps, see fiber_all in Chapter 9.)

Avoid Deep Nesting Within the Block

The macro eliminates nesting between flat_map chains. But you can still create nested effect! blocks, which gets confusing:

#![allow(unused)]
fn main() {
// CONFUSING — nested macro bodies
effect! {
    let result = ~ effect! {      // inner macro
        let x = ~ inner_step();
        x * 2
    };
    result + 1
}

// BETTER — flatten it
effect! {
    let x = ~ inner_step();
    let result = x * 2;
    result + 1
}
}

If you feel the urge to nest effect! inside effect!, flatten the outer block instead.

The Macro and Type Inference

The macro occasionally confuses the type inferencer, especially when the error type isn't pinned early. If you see cryptic "can't infer type" errors inside effect!:

  1. Annotate the return type of the enclosing function explicitly
  2. Add a .map_error(Into::into) on the first ~ binding to anchor E
  3. As a last resort, break out the inner logic into a named helper function

When Generic Returns Are Needed

Library code with polymorphic A, E, R sometimes can't use the macro cleanly:

#![allow(unused)]
fn main() {
// This works fine with explicit function + effect!
pub fn load_config<A, E, R>() -> Effect<A, E, R>
where
    A: From<Config> + 'static,
    E: From<ConfigError> + 'static,
    R: 'static,
{
    effect!(|_r: &mut R| {
        let cfg = read_env_config()?;
        A::from(cfg)
    })
}
}

The closure form of effect! (with |_r: &mut R|) is the right tool for generic graph-builder functions. It's still the macro, just in its raw form.

Summary

SituationPrefer
2+ sequential stepseffect! { ~ ... }
1 step, simple transform.map / .flat_map
Independent steps.zip / combinators
Generic <A, E, R> graph builder`effect!(
Structural patterns (zip, race, all)explicit combinators, not macro

The macro is a tool, not a religion. Use it when it makes the code read like a story; use combinators when they express intent more directly.