diff --git a/src/decider.rs b/src/decider.rs index e9a2e36..9b299a9 100644 --- a/src/decider.rs +++ b/src/decider.rs @@ -1,4 +1,7 @@ -use crate::{DecideFunction, EvolveFunction, InitialStateFunction, Sum}; +use crate::{ + DecideFunction, Decider3, Decider4, Decider5, Decider6, EvolveFunction, InitialStateFunction, + Sum, Sum3, Sum4, Sum5, Sum6, +}; /// [Decider] represents the main decision-making algorithm. /// It has three generic parameters `C`/`Command`, `S`/`State`, `E`/`Event` , representing the type of the values that Decider may contain or use. @@ -309,6 +312,284 @@ impl<'a, C, S, E, Error> Decider<'a, C, S, E, Error> { initial_state: new_initial_state, } } + + /// Combines three deciders into one bigger decider + pub fn combine3( + self, + decider2: Decider<'a, C2, S2, E2, Error>, + decider3: Decider<'a, C3, S3, E3, Error>, + ) -> Decider3<'a, C, C2, C3, S, S2, S3, E, E2, E3, Error> + where + S: Clone, + S2: Clone, + S3: Clone, + E: Clone, + E2: Clone, + E3: Clone, + C: Clone, + C2: Clone, + C3: Clone, + { + // First combine self with decider2 + let combined = self.combine(decider2); + + // Then combine with decider3 and map the types + combined + .combine(decider3) + .map_state( + &|s: &(S, S2, S3)| ((s.0.clone(), s.1.clone()), s.2.clone()), + &|s: &((S, S2), S3)| (s.0 .0.clone(), s.0 .1.clone(), s.1.clone()), + ) + .map_event( + &|e: &Sum3| match e { + Sum3::First(ref e) => Sum::First(Sum::First(e.clone())), + Sum3::Second(ref e) => Sum::First(Sum::Second(e.clone())), + Sum3::Third(ref e) => Sum::Second(e.clone()), + }, + &|e| match e { + Sum::First(Sum::First(e)) => Sum3::First(e.clone()), + Sum::First(Sum::Second(e)) => Sum3::Second(e.clone()), + Sum::Second(e) => Sum3::Third(e.clone()), + }, + ) + .map_command(&|c: &Sum3| match c { + Sum3::First(c) => Sum::First(Sum::First(c.clone())), + Sum3::Second(c) => Sum::First(Sum::Second(c.clone())), + Sum3::Third(c) => Sum::Second(c.clone()), + }) + } + + #[allow(clippy::type_complexity)] + /// Combines four deciders into one bigger decider + pub fn combine4( + self, + decider2: Decider<'a, C2, S2, E2, Error>, + decider3: Decider<'a, C3, S3, E3, Error>, + decider4: Decider<'a, C4, S4, E4, Error>, + ) -> Decider4<'a, C, C2, C3, C4, S, S2, S3, S4, E, E2, E3, E4, Error> + where + S: Clone, + S2: Clone, + S3: Clone, + S4: Clone, + E: Clone, + E2: Clone, + E3: Clone, + E4: Clone, + C: Clone, + C2: Clone, + C3: Clone, + C4: Clone, + { + let combined = self + .combine(decider2) + .combine(decider3) + .combine(decider4) + .map_state( + &|s: &(S, S2, S3, S4)| (((s.0.clone(), s.1.clone()), s.2.clone()), s.3.clone()), + &|s: &(((S, S2), S3), S4)| { + ( + s.0 .0 .0.clone(), + s.0 .0 .1.clone(), + s.0 .1.clone(), + s.1.clone(), + ) + }, + ) + .map_event( + &|e: &Sum4| match e { + Sum4::First(e) => Sum::First(Sum::First(Sum::First(e.clone()))), + Sum4::Second(e) => Sum::First(Sum::First(Sum::Second(e.clone()))), + Sum4::Third(e) => Sum::First(Sum::Second(e.clone())), + Sum4::Fourth(e) => Sum::Second(e.clone()), + }, + &|e| match e { + Sum::First(Sum::First(Sum::First(e))) => Sum4::First(e.clone()), + Sum::First(Sum::First(Sum::Second(e))) => Sum4::Second(e.clone()), + Sum::First(Sum::Second(e)) => Sum4::Third(e.clone()), + Sum::Second(e) => Sum4::Fourth(e.clone()), + }, + ) + .map_command(&|c: &Sum4| match c { + Sum4::First(c) => Sum::First(Sum::First(Sum::First(c.clone()))), + Sum4::Second(c) => Sum::First(Sum::First(Sum::Second(c.clone()))), + Sum4::Third(c) => Sum::First(Sum::Second(c.clone())), + Sum4::Fourth(c) => Sum::Second(c.clone()), + }); + combined + } + + #[allow(clippy::type_complexity)] + /// Combines five deciders into one bigger decider + pub fn combine5( + self, + decider2: Decider<'a, C2, S2, E2, Error>, + decider3: Decider<'a, C3, S3, E3, Error>, + decider4: Decider<'a, C4, S4, E4, Error>, + decider5: Decider<'a, C5, S5, E5, Error>, + ) -> Decider5<'a, C, C2, C3, C4, C5, S, S2, S3, S4, S5, E, E2, E3, E4, E5, Error> + where + S: Clone, + S2: Clone, + S3: Clone, + S4: Clone, + S5: Clone, + E: Clone, + E2: Clone, + E3: Clone, + E4: Clone, + E5: Clone, + C: Clone, + C2: Clone, + C3: Clone, + C4: Clone, + C5: Clone, + { + let combined = self + .combine(decider2) + .combine(decider3) + .combine(decider4) + .combine(decider5) + .map_state( + &|s: &(S, S2, S3, S4, S5)| { + ( + (((s.0.clone(), s.1.clone()), s.2.clone()), s.3.clone()), + s.4.clone(), + ) + }, + &|s: &((((S, S2), S3), S4), S5)| { + ( + s.0 .0 .0 .0.clone(), + s.0 .0 .0 .1.clone(), + s.0 .0 .1.clone(), + s.0 .1.clone(), + s.1.clone(), + ) + }, + ) + .map_event( + &|e: &Sum5| match e { + Sum5::First(e) => Sum::First(Sum::First(Sum::First(Sum::First(e.clone())))), + Sum5::Second(e) => Sum::First(Sum::First(Sum::First(Sum::Second(e.clone())))), + Sum5::Third(e) => Sum::First(Sum::First(Sum::Second(e.clone()))), + Sum5::Fourth(e) => Sum::First(Sum::Second(e.clone())), + Sum5::Fifth(e) => Sum::Second(e.clone()), + }, + &|e| match e { + Sum::First(Sum::First(Sum::First(Sum::First(e)))) => Sum5::First(e.clone()), + Sum::First(Sum::First(Sum::First(Sum::Second(e)))) => Sum5::Second(e.clone()), + Sum::First(Sum::First(Sum::Second(e))) => Sum5::Third(e.clone()), + Sum::First(Sum::Second(e)) => Sum5::Fourth(e.clone()), + Sum::Second(e) => Sum5::Fifth(e.clone()), + }, + ) + .map_command(&|c: &Sum5| match c { + Sum5::First(c) => Sum::First(Sum::First(Sum::First(Sum::First(c.clone())))), + Sum5::Second(c) => Sum::First(Sum::First(Sum::First(Sum::Second(c.clone())))), + Sum5::Third(c) => Sum::First(Sum::First(Sum::Second(c.clone()))), + Sum5::Fourth(c) => Sum::First(Sum::Second(c.clone())), + Sum5::Fifth(c) => Sum::Second(c.clone()), + }); + combined + } + + #[allow(clippy::type_complexity)] + /// Combines six deciders into one bigger decider + pub fn combine6( + self, + decider2: Decider<'a, C2, S2, E2, Error>, + decider3: Decider<'a, C3, S3, E3, Error>, + decider4: Decider<'a, C4, S4, E4, Error>, + decider5: Decider<'a, C5, S5, E5, Error>, + decider6: Decider<'a, C6, S6, E6, Error>, + ) -> Decider6<'a, C, C2, C3, C4, C5, C6, S, S2, S3, S4, S5, S6, E, E2, E3, E4, E5, E6, Error> + where + S: Clone, + S2: Clone, + S3: Clone, + S4: Clone, + S5: Clone, + S6: Clone, + E: Clone, + E2: Clone, + E3: Clone, + E4: Clone, + E5: Clone, + E6: Clone, + C: Clone, + C2: Clone, + C3: Clone, + C4: Clone, + C5: Clone, + C6: Clone, + { + let combined = self + .combine(decider2) + .combine(decider3) + .combine(decider4) + .combine(decider5) + .combine(decider6) + .map_state( + &|s: &(S, S2, S3, S4, S5, S6)| { + ( + ( + (((s.0.clone(), s.1.clone()), s.2.clone()), s.3.clone()), + s.4.clone(), + ), + s.5.clone(), + ) + }, + &|s: &(((((S, S2), S3), S4), S5), S6)| { + ( + s.0 .0 .0 .0 .0.clone(), + s.0 .0 .0 .0 .1.clone(), + s.0 .0 .0 .1.clone(), + s.0 .0 .1.clone(), + s.0 .1.clone(), + s.1.clone(), + ) + }, + ) + .map_event( + &|e: &Sum6| match e { + Sum6::First(e) => { + Sum::First(Sum::First(Sum::First(Sum::First(Sum::First(e.clone()))))) + } + Sum6::Second(e) => { + Sum::First(Sum::First(Sum::First(Sum::First(Sum::Second(e.clone()))))) + } + Sum6::Third(e) => Sum::First(Sum::First(Sum::First(Sum::Second(e.clone())))), + Sum6::Fourth(e) => Sum::First(Sum::First(Sum::Second(e.clone()))), + Sum6::Fifth(e) => Sum::First(Sum::Second(e.clone())), + Sum6::Sixth(e) => Sum::Second(e.clone()), + }, + &|e| match e { + Sum::First(Sum::First(Sum::First(Sum::First(Sum::First(e))))) => { + Sum6::First(e.clone()) + } + Sum::First(Sum::First(Sum::First(Sum::First(Sum::Second(e))))) => { + Sum6::Second(e.clone()) + } + Sum::First(Sum::First(Sum::First(Sum::Second(e)))) => Sum6::Third(e.clone()), + Sum::First(Sum::First(Sum::Second(e))) => Sum6::Fourth(e.clone()), + Sum::First(Sum::Second(e)) => Sum6::Fifth(e.clone()), + Sum::Second(e) => Sum6::Sixth(e.clone()), + }, + ) + .map_command(&|c: &Sum6| match c { + Sum6::First(c) => { + Sum::First(Sum::First(Sum::First(Sum::First(Sum::First(c.clone()))))) + } + Sum6::Second(c) => { + Sum::First(Sum::First(Sum::First(Sum::First(Sum::Second(c.clone()))))) + } + Sum6::Third(c) => Sum::First(Sum::First(Sum::First(Sum::Second(c.clone())))), + Sum6::Fourth(c) => Sum::First(Sum::First(Sum::Second(c.clone()))), + Sum6::Fifth(c) => Sum::First(Sum::Second(c.clone())), + Sum6::Sixth(c) => Sum::Second(c.clone()), + }); + combined + } } /// Formalizes the `Event Computation` algorithm / event sourced system for the `decider` to handle commands based on the current events, and produce new events. diff --git a/src/lib.rs b/src/lib.rs index 0c74fbe..3b6051e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -307,7 +307,10 @@ //! --- //! Created with `love` by [Fraktalio](https://!fraktalio.com/) +use decider::Decider; +use saga::Saga; use serde::{Deserialize, Serialize}; +use view::View; /// Aggregate module - belongs to the `Application` layer - composes pure logic and effects (fetching, storing) pub mod aggregate; @@ -332,7 +335,7 @@ pub type InitialStateFunction<'a, S> = Box S + 'a + Send + Sync>; /// The [ReactFunction] function is used to decide what actions/A to execute next based on the action result/AR. pub type ReactFunction<'a, AR, A> = Box Vec + 'a + Send + Sync>; -/// Define the generic Combined/Sum Enum +/// Generic Combined/Sum Enum of two variants #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub enum Sum { /// First variant @@ -341,6 +344,108 @@ pub enum Sum { Second(B), } +/// Generic Combined/Sum Enum of three variants +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +pub enum Sum3 { + /// First variant + First(A), + /// Second variant + Second(B), + /// Third variant + Third(C), +} + +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +/// Generic Combined/Sum Enum of four variants +pub enum Sum4 { + /// First variant + First(A), + /// Second variant + Second(B), + /// Third variant + Third(C), + /// Fourth variant + Fourth(D), +} + +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +/// Generic Combined/Sum Enum of five variants +pub enum Sum5 { + /// First variant + First(A), + /// Second variant + Second(B), + /// Third variant + Third(C), + /// Fourth variant + Fourth(D), + /// Fifth variant + Fifth(E), +} + +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +/// Generic Combined/Sum Enum of six variants +pub enum Sum6 { + /// First variant + First(A), + /// Second variant + Second(B), + /// Third variant + Third(C), + /// Fourth variant + Fourth(D), + /// Fifth variant + Fifth(E), + /// Sixth variant + Sixth(F), +} + +/// Convenient type alias that represents 3 combined Deciders +type Decider3<'a, C1, C2, C3, S1, S2, S3, E1, E2, E3, Error> = + Decider<'a, Sum3, (S1, S2, S3), Sum3, Error>; + +/// Convenient type alias that represents 4 combined Deciders +type Decider4<'a, C1, C2, C3, C4, S1, S2, S3, S4, E1, E2, E3, E4, Error> = + Decider<'a, Sum4, (S1, S2, S3, S4), Sum4, Error>; + +/// Convenient type alias that represents 5 combined Deciders +type Decider5<'a, C1, C2, C3, C4, C5, S1, S2, S3, S4, S5, E1, E2, E3, E4, E5, Error> = + Decider<'a, Sum5, (S1, S2, S3, S4, S5), Sum5, Error>; + +/// Convenient type alias that represents 6 combined Deciders +type Decider6<'a, C1, C2, C3, C4, C5, C6, S1, S2, S3, S4, S5, S6, E1, E2, E3, E4, E5, E6, Error> = + Decider< + 'a, + Sum6, + (S1, S2, S3, S4, S5, S6), + Sum6, + Error, + >; + +/// Convenient type alias that represents 3 merged Views +type View3<'a, S1, S2, S3, E> = View<'a, (S1, S2, S3), E>; + +/// Convenient type alias that represents 4 merged Deciders +type View4<'a, S1, S2, S3, S4, E> = View<'a, (S1, S2, S3, S4), E>; + +/// Convenient type alias that represents 5 merged Deciders +type View5<'a, S1, S2, S3, S4, S5, E> = View<'a, (S1, S2, S3, S4, S5), E>; + +/// Convenient type alias that represents 6 merged Deciders +type View6<'a, S1, S2, S3, S4, S5, S6, E> = View<'a, (S1, S2, S3, S4, S5, S6), E>; + +/// Convenient type alias that represents 3 merged Sagas +type Saga3<'a, AR, A1, A2, A3> = Saga<'a, AR, Sum3>; + +/// Convenient type alias that represents 4 merged Sagas +type Saga4<'a, AR, A1, A2, A3, A4> = Saga<'a, AR, Sum4>; + +/// Convenient type alias that represents 5 merged Sagas +type Saga5<'a, AR, A1, A2, A3, A4, A5> = Saga<'a, AR, Sum5>; + +/// Convenient type alias that represents 6 merged Sagas +type Saga6<'a, AR, A1, A2, A3, A4, A5, A6> = Saga<'a, AR, Sum6>; + /// Identify the state/command/event. /// It is used to identify the concept to what the state/command/event belongs to. For example, the `order_id` or `restaurant_id`. pub trait Identifier { diff --git a/src/saga.rs b/src/saga.rs index 4f8c319..c97641c 100644 --- a/src/saga.rs +++ b/src/saga.rs @@ -1,4 +1,4 @@ -use crate::{ReactFunction, Sum}; +use crate::{ReactFunction, Saga3, Saga4, Saga5, Saga6, Sum, Sum3, Sum4, Sum5, Sum6}; /// [Saga] is a datatype that represents the central point of control, deciding what to execute next (`A`), based on the action result (`AR`). /// It has two generic parameters `AR`/Action Result, `A`/Action , representing the type of the values that Saga may contain or use. @@ -156,6 +156,118 @@ impl<'a, AR, A> Saga<'a, AR, A> { Saga { react: new_react } } + + /// Merges three sagas into one. + pub fn merge3( + self, + saga2: Saga<'a, AR, A2>, + saga3: Saga<'a, AR, A3>, + ) -> Saga3<'a, AR, A, A2, A3> + where + A: Clone, + A2: Clone, + A3: Clone, + { + self.merge(saga2) + .merge(saga3) + .map_action(&|a: &Sum>| match a { + Sum::First(a) => Sum3::Third(a.clone()), + Sum::Second(Sum::First(a)) => Sum3::Second(a.clone()), + Sum::Second(Sum::Second(a)) => Sum3::First(a.clone()), + }) + } + + /// Merges four sagas into one. + pub fn merge4( + self, + saga2: Saga<'a, AR, A2>, + saga3: Saga<'a, AR, A3>, + saga4: Saga<'a, AR, A4>, + ) -> Saga4<'a, AR, A, A2, A3, A4> + where + A: Clone, + A2: Clone, + A3: Clone, + A4: Clone, + { + self.merge(saga2) + .merge(saga3) + .merge(saga4) + .map_action(&|a: &Sum>>| match a { + Sum::First(a) => Sum4::Fourth(a.clone()), + Sum::Second(Sum::First(a)) => Sum4::Third(a.clone()), + Sum::Second(Sum::Second(Sum::First(a))) => Sum4::Second(a.clone()), + Sum::Second(Sum::Second(Sum::Second(a))) => Sum4::First(a.clone()), + }) + } + + #[allow(clippy::type_complexity)] + /// Merges five sagas into one. + pub fn merge5( + self, + saga2: Saga<'a, AR, A2>, + saga3: Saga<'a, AR, A3>, + saga4: Saga<'a, AR, A4>, + saga5: Saga<'a, AR, A5>, + ) -> Saga5<'a, AR, A, A2, A3, A4, A5> + where + A: Clone, + A2: Clone, + A3: Clone, + A4: Clone, + A5: Clone, + { + self.merge(saga2) + .merge(saga3) + .merge(saga4) + .merge(saga5) + .map_action(&|a: &Sum>>>| match a { + Sum::First(a) => Sum5::Fifth(a.clone()), + Sum::Second(Sum::First(a)) => Sum5::Fourth(a.clone()), + Sum::Second(Sum::Second(Sum::First(a))) => Sum5::Third(a.clone()), + Sum::Second(Sum::Second(Sum::Second(Sum::First(a)))) => Sum5::Second(a.clone()), + Sum::Second(Sum::Second(Sum::Second(Sum::Second(a)))) => Sum5::First(a.clone()), + }) + } + + #[allow(clippy::type_complexity)] + /// Merges six sagas into one. + pub fn merge6( + self, + saga2: Saga<'a, AR, A2>, + saga3: Saga<'a, AR, A3>, + saga4: Saga<'a, AR, A4>, + saga5: Saga<'a, AR, A5>, + saga6: Saga<'a, AR, A6>, + ) -> Saga6<'a, AR, A, A2, A3, A4, A5, A6> + where + A: Clone, + A2: Clone, + A3: Clone, + A4: Clone, + A5: Clone, + A6: Clone, + { + self.merge(saga2) + .merge(saga3) + .merge(saga4) + .merge(saga5) + .merge(saga6) + .map_action( + &|a: &Sum>>>>| match a { + Sum::First(a) => Sum6::Sixth(a.clone()), + Sum::Second(Sum::First(a)) => Sum6::Fifth(a.clone()), + Sum::Second(Sum::Second(Sum::First(a))) => Sum6::Fourth(a.clone()), + Sum::Second(Sum::Second(Sum::Second(Sum::First(a)))) => Sum6::Third(a.clone()), + Sum::Second(Sum::Second(Sum::Second(Sum::Second(Sum::First(a))))) => { + Sum6::Second(a.clone()) + } + Sum::Second(Sum::Second(Sum::Second(Sum::Second(Sum::Second(a))))) => { + Sum6::First(a.clone()) + } + }, + ) + } } /// Formalizes the `Action Computation` algorithm for the `saga` to handle events/action_results, and produce new commands/actions. diff --git a/src/view.rs b/src/view.rs index 220475f..a054b3e 100644 --- a/src/view.rs +++ b/src/view.rs @@ -1,4 +1,4 @@ -use crate::{EvolveFunction, InitialStateFunction, Sum}; +use crate::{EvolveFunction, InitialStateFunction, Sum, View3, View4, View5, View6}; /// [View] represents the event handling algorithm, responsible for translating the events into denormalized state, which is more adequate for querying. /// It has two generic parameters `S`/State, `E`/Event , representing the type of the values that View may contain or use. @@ -191,6 +191,134 @@ impl<'a, S, E> View<'a, S, E> { initial_state: new_initial_state, } } + + /// Merges three views into one. + pub fn merge3( + self, + view2: View<'a, S2, E>, + view3: View<'a, S3, E>, + ) -> View3<'a, S, S2, S3, E> + where + S: Clone, + S2: Clone, + S3: Clone, + { + self.merge(view2).merge(view3).map_state( + &|s: &(S, S2, S3)| ((s.0.clone(), s.1.clone()), s.2.clone()), + &|s: &((S, S2), S3)| (s.0 .0.clone(), s.0 .1.clone(), s.1.clone()), + ) + } + + /// Merges four views into one. + pub fn merge4( + self, + view2: View<'a, S2, E>, + view3: View<'a, S3, E>, + view4: View<'a, S4, E>, + ) -> View4<'a, S, S2, S3, S4, E> + where + S: Clone, + S2: Clone, + S3: Clone, + S4: Clone, + { + self.merge(view2).merge(view3).merge(view4).map_state( + &|s: &(S, S2, S3, S4)| (((s.0.clone(), s.1.clone()), s.2.clone()), s.3.clone()), + &|s: &(((S, S2), S3), S4)| { + ( + s.0 .0 .0.clone(), + s.0 .0 .1.clone(), + s.0 .1.clone(), + s.1.clone(), + ) + }, + ) + } + + #[allow(clippy::type_complexity)] + /// Merges five views into one. + pub fn merge5( + self, + view2: View<'a, S2, E>, + view3: View<'a, S3, E>, + view4: View<'a, S4, E>, + view5: View<'a, S5, E>, + ) -> View5<'a, S, S2, S3, S4, S5, E> + where + S: Clone, + S2: Clone, + S3: Clone, + S4: Clone, + S5: Clone, + { + self.merge(view2) + .merge(view3) + .merge(view4) + .merge(view5) + .map_state( + &|s: &(S, S2, S3, S4, S5)| { + ( + (((s.0.clone(), s.1.clone()), s.2.clone()), s.3.clone()), + s.4.clone(), + ) + }, + &|s: &((((S, S2), S3), S4), S5)| { + ( + s.0 .0 .0 .0.clone(), + s.0 .0 .0 .1.clone(), + s.0 .0 .1.clone(), + s.0 .1.clone(), + s.1.clone(), + ) + }, + ) + } + + #[allow(clippy::type_complexity)] + /// Merges six views into one. + pub fn merge6( + self, + view2: View<'a, S2, E>, + view3: View<'a, S3, E>, + view4: View<'a, S4, E>, + view5: View<'a, S5, E>, + view6: View<'a, S6, E>, + ) -> View6<'a, S, S2, S3, S4, S5, S6, E> + where + S: Clone, + S2: Clone, + S3: Clone, + S4: Clone, + S5: Clone, + S6: Clone, + { + self.merge(view2) + .merge(view3) + .merge(view4) + .merge(view5) + .merge(view6) + .map_state( + &|s: &(S, S2, S3, S4, S5, S6)| { + ( + ( + (((s.0.clone(), s.1.clone()), s.2.clone()), s.3.clone()), + s.4.clone(), + ), + s.5.clone(), + ) + }, + &|s: &(((((S, S2), S3), S4), S5), S6)| { + ( + s.0 .0 .0 .0 .0.clone(), + s.0 .0 .0 .0 .1.clone(), + s.0 .0 .0 .1.clone(), + s.0 .0 .1.clone(), + s.0 .1.clone(), + s.1.clone(), + ) + }, + ) + } } /// Formalizes the `State Computation` algorithm for the `view` to handle events based on the current state, and produce new state.