Module composable::derive_macros
source · Expand description
Derive macros used to ease the creation of recursive reducers.
RecursiveReducer
#[derive(RecursiveReducer)]
on aenum
orstruct
that contains otherReducer
types will derive aReducer
implementation for it.TryInto
#[derive(TryInto)]
on aAction
whose variants contain anotherReducer
’sAction
s allows an attempted conversion to…From
#[derive(TryInto)]
on aAction
whose variants contain anotherReducer
’sAction
s 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 Reducer
s 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 Reducer
s.
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 mod
ules representing the different Domains or
Features of the application; each with its own Action
s and State
.
These Reducer
s are then collected into various composite Reducer
s that contain and
coordinate between them.
Each composite Reducer
is written with the knowledge of its own Action
s and the Action
s
of its immediate children. The Action
s of its parent are unknown to it and (by convention) it
does not traffic in the Action
s 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 State
s 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
Action
s are being passed to multipleReducers
they must beClone
. -
The
RecursiveReducer
derive macro constructs a recursiveReducer
from thestruct
. -
The
From
andTryInfo
derive macros ensure that conversions work, when they should, between parent and childAction
s. These conversions utilize #4… -
The parent has one (and only one)
Action
for theAction
s of each of its children. -
Finally, an implementation of the
RecursiveReducer
trait containing the parent’sreduce
method.RecursiveReducer::reduce
is run before theReducer::reduce
methods of its fields. Resulting in:self.reduce()
, thenself.a.reduce()
, thenself.b.reduce()
.
§Ignoring fields
Compound Reducer
s often contain fields other than the child Reducer
s. 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 Reducer
s are less common than Composite Reducer
s 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::Action
s 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
RecursiveReducer
macro for example usage.
Derive Macros§
- Compiler Errors