Macro-Based Ergonomics in fx-rs
fx-rs uses Rust's macro system to provide ergonomic APIs and reduce boilerplate for effectful programming. Some highlights:
Ability Derivation
Procedural macros in the abilities_macro
crate allow you to derive ability traits and handlers automatically, making it easy to define new effectful operations and their interpreters.
Direct-Style Macros
The do_macro
and do_traits
crates provide macros for writing effectful code in a direct, imperative style, reducing the need for manual chaining and improving readability.
Example: fx_do! with .same() and .bind()
The fx_do!
macro allows you to write effectful code in a direct style. Under the hood, .same()
is used for map_m
(monadic map), and .bind()
is used for flat_map
(monadic bind):
#![allow(unused)] fn main() { use fx::Fx; use fx_do::fx_do; // Type alias for macro-based effect type MacroEff = Fx<MacroEnv, MacroVal>; fx_do! { let x: MacroEff = Fx::pure(1); let y = x.same(); // equivalent to .map_m let z = y.bind(); // equivalent to .flat_map Fx::pure(z) } }
Lens and Field Macros
The lens_macro
, field_macro
, and forall_macro
crates provide macros for generating lenses and working with generic fields, enabling fine-grained, type-safe state manipulation and modular effect composition.
field_macro: Field-Based Lenses
The field_macro
crate provides macros to automatically generate lenses for struct fields, making it easy to focus on and update nested fields in your state.
Example: field_macro
#![allow(unused)] fn main() { use field_macro::Field; #[derive(Field, Clone)] struct AppState { #[field] counter: u32, #[field] user: User, } // Now you can use AppState::counter() and AppState::user() as lenses. }
forall_macro: Quantified Field Access
The forall_macro
crate provides macros for working with generic fields and quantified access, enabling advanced patterns for generic and reusable effectful code.
ContextBuilder Macro: Effectful Context Provision
The builder_macro
crate provides a procedural macro for generating type-safe, incremental context builders for struct contexts. This enables ergonomic and effectful context provision, especially when working with fx-rs's effect system and trait-based dependency injection.
Example: Using ContextBuilder for Effectful Contexts
#![allow(unused)] fn main() { use builder_macro::ContextBuilder; use fx::{Has, Put}; #[derive(ContextBuilder, Debug, Clone)] struct MyContext { a: i32, b: String, c: bool, } // Incrementally build the context let ctx = MyContextBuilder::empty() .a(42) .b("hello".to_string()) .c(true) .build(); // Use trait-based access assert_eq!(Has::<i32>::get(ctx.clone()), 42); assert_eq!(Has::<String>::get(ctx.clone()), "hello"); assert_eq!(Has::<bool>::get(ctx.clone()), true); // Use macro-generated accessors assert_eq!(ctx.a(), Some(42)); assert!(ctx.has_a()); // Compose with fx-rs effect system // (see fx.rs book for more advanced examples) }
The macro generates:
- A builder type with
put_*
methods for each field - Marker types for tracking field presence
build()
method (requires all fields set)- Trait impls for
Has
andPut
- Field accessors and presence checks
This pattern enables ergonomic, type-safe context provision and mutation for effectful code.
These macros are designed to work seamlessly with the fx-rs core, making advanced effect patterns accessible and ergonomic for Rust developers.