Module composable::derive_macros
source · Expand description
Derive macros used to ease the creation of recursive reducers.
RecursiveReducer
#[derive(RecursiveReducer)]on aenumorstructthat contains otherReducertypes will derive aReducerimplementation for it.TryInto
#[derive(TryInto)]on aActionwhose variants contain anotherReducer’sActions allows an attempted conversion to…From
#[derive(TryInto)]on aActionwhose variants contain anotherReducer’sActions allows an attempted conversion from…
These macros produce efficient implementations of the Reducer, std::convert::TryInto
and std::convert::From traits so that they do not have to be implemented manually.
§Automatic Derived Reducers
Two other types are valid Reducers whenever they contain a Reducer.
These do not require the RecursiveReducer and automatically apply.
§Composite Reducers
A RecursiveReducer struct represents a parent-child relationship between Reducers.
This is the most common use of RecursiveReducer in large applications and forms the core
“Composability” of the Composable Architecture.
The application is broken up into different modules representing the different Domains or
Features of the application; each with its own Actions and State.
These Reducers are then collected into various composite Reducers that contain and
coordinate between them.
Each composite Reducer is written with the knowledge of its own Actions and the Actions
of its immediate children. The Actions of its parent are unknown to it and (by convention) it
does not traffic in the Actions of its grandchildren.
Deciding which Domains need to be coordinated between, and thus should be siblings under a parent Domains, is the art of designing the application with an architecture like this one.
Even though the application struct recursively contains the States of all of its Features
it usually does not end up being very “tall.”
See Unidirectional Event Architecture for more.
mod A {
#[derive(Default)]
pub struct State { /* … */ }
#[derive(Clone)] // ⒈
pub enum Action { /* … */ }
impl Reducer for State {
type Action = Action;
type Output = Self;
fn reduce(&mut self, action: Action, send: impl Effects<Action>) {
match action { /* … */ }
}
}
}
mod B {
#[derive(Default)]
pub struct State;
#[derive(Clone)] // ⒈
pub enum Action { /* … */ }
impl Reducer for State {
type Action = Action;
type Output = Self;
fn reduce(&mut self, action: Action, send: impl Effects<Action>) {
match action { /* … */ }
}
}
}
#[derive(Default, RecursiveReducer)] // ⒉
struct State {
a: A::State,
b: B::State,
}
#[derive(Clone, From, TryInto)] // ⒊
enum Action {
SomeAction, // parent actions
SomeOtherAction,
A(A::Action), // ⒋
B(B::Action),
}
impl RecursiveReducer for State { // ⒌
type Action = Action;
fn reduce(&mut self, action: Action, send: impl Effects<Action>) {
match action {
Action::SomeAction => { /* … */ }
Action::SomeOtherAction => { /* … */ }
// in this example, the parent reducer has
// no explicit handling of any child actions
_ => {}
}
}
}
-
Now that
Actions are being passed to multipleReducersthey must beClone. -
The
RecursiveReducerderive macro constructs a recursiveReducerfrom thestruct. -
The
FromandTryInfoderive macros ensure that conversions work, when they should, between parent and childActions. These conversions utilize #4… -
The parent has one (and only one)
Actionfor theActions of each of its children. -
Finally, an implementation of the
RecursiveReducertrait containing the parent’sreducemethod.RecursiveReducer::reduceis run before theReducer::reducemethods of its fields. Resulting in:self.reduce(), thenself.a.reduce(), thenself.b.reduce().
§Ignoring fields
Compound Reducers often contain fields other than the child Reducers. After all, it has
its own Reducer and that Reducer may need its own state.
The RecursiveReducer macro comes with an associated attribute that allows it to skip
struct members that should not ne made part of the Reducer recursion.
#[derive(RecursiveReducer)]
struct State {
a: A::State,
b: B::State,
#[reducer(skip)]
c: Vec<u32>,
}§Alternate Reducers
A RecursiveReducer enum represents a single state that is best
represented by an enumeration a separate reducers.
Alternate Reducers are less common than Composite Reducers so a more concrete example may
help…
#[derive(RecursiveReducer)]
enum State {
LoggedIn(authenticated::State),
LoggedOut(unauthenticated::State),
}
#[derive(Clone, From, TryInto)]
enum Action {
LoggedIn(authenticated::Action),
LoggedOut(unauthenticated::Action),
}
impl RecursiveReducer for State {
type Action = Action;
fn reduce(&mut self, action: Action, send: impl Effects<Action>) {
// logic independent of the user’s authentication
}
}authenticated::Actions will only run when the state is LoggedIn and vice-versa..
Now, the automatic derive reducer behavior of Option is easy to described.
It behaves is as if it were:
#[derive(RecursiveReducer)]
enum Option<T: Reducer> {
#[reducer(skip)]
None,
Some(T),
}Although, currently, the RecursiveReducer macro does not work with generic parameters on the
type it is attempting to derive the Reducer trait for.
Re-exports§
pub use derive_more::From;pub use derive_more::TryInto;
Traits§
- See the
RecursiveReducermacro for example usage.
Derive Macros§
- Compiler Errors