1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
//! Defines the [`ApplicableTo`] trait and related types.
use std::sync::Arc;
use crate::log_entry::LogEntry;
use crate::state::LogEntryOf;
use crate::state::OutcomeOf;
use crate::state::State;
/// Shorthand to extract `Projected` type out of `A as ApplicableTo<S>`.
pub type ProjectedOf<A, S> = <ProjectionOf<A, S> as Projection<OutcomeOf<S>>>::Projected;
/// Shorthand to extract `Projection` type out of `A as ApplicableTo<S>`.
pub type ProjectionOf<A, S> = <A as ApplicableTo<S>>::Projection;
/// Describes values that may be [applied][State::apply] to type `S`.
///
/// For any given `State` implementation there is usually a wide range of
/// possible operations. These are encoded by its corresponding `LogEntry` type,
/// which commonly contains (or is) an enum value whose variants correspond to
/// the state's operations. Different operations usually have different
/// outcome types, which is why [State::Outcome] is also commonly an enum.
///
/// This presents an ergonomics challenge. Imagine `enum MyLogEntry { A, B }`
/// and `enum MyOutcome { A(i64), B(bool) }`. When appending a `MyLogEntry::A`
/// we'd like to get back an `i64` rather than a `MyOutcome`. This can be
/// achieved as follows.
///
/// ```
/// # use std::sync::Arc;
/// #
/// # #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
/// # enum MyLogEntry {
/// # A,
/// # B,
/// # }
/// #
/// # #[derive(Clone, Debug)]
/// # enum MyOutcome {
/// # A(i64),
/// # B(bool),
/// # }
/// #
/// # #[derive(Clone, Debug)]
/// # struct MyState;
/// #
/// # impl paxakos::LogEntry for MyLogEntry {
/// # type Id = ();
/// #
/// # fn id(&self) -> Self::Id {
/// # unimplemented!()
/// # }
/// # }
/// # impl paxakos::State for MyState {
/// # type LogEntry = MyLogEntry;
/// #
/// # type Context = ();
/// #
/// # type Outcome = MyOutcome;
/// #
/// # type Effect = ();
/// #
/// # type Node = ();
/// #
/// # fn apply(
/// # &mut self,
/// # _log_entry: &Self::LogEntry,
/// # _context: &mut Self::Context,
/// # ) -> (Self::Outcome, Self::Effect) {
/// # unimplemented!()
/// # }
/// #
/// # fn cluster_at(&self, _round_offset: std::num::NonZeroUsize) -> Vec<Self::Node> {
/// # unimplemented!()
/// # }
/// # }
/// #
/// struct A;
///
/// impl paxakos::applicable::ApplicableTo<MyState> for A {
/// type Projection = ProjectionA;
///
/// fn into_log_entry(self) -> Arc<MyLogEntry> {
/// Arc::new(MyLogEntry::A)
/// }
/// }
///
/// struct ProjectionA;
///
/// impl paxakos::applicable::Projection<MyOutcome> for ProjectionA {
/// type Projected = i64;
///
/// fn project(val: MyOutcome) -> Self::Projected {
/// match val {
/// MyOutcome::A(i) => i,
/// _ => panic!("unexpected: {:?}", val)
/// }
/// }
/// }
/// ```
pub trait ApplicableTo<S: State> {
/// Projection type, usually a zero-sized type.
type Projection: Projection<OutcomeOf<S>>;
/// Turns this applicable value into a log entry.
fn into_log_entry(self) -> Arc<LogEntryOf<S>>;
}
impl<S: State<LogEntry = E>, E: LogEntry> ApplicableTo<S> for E {
type Projection = Identity;
fn into_log_entry(self) -> Arc<<S as State>::LogEntry> {
Arc::new(self)
}
}
impl<S: State<LogEntry = E>, E: LogEntry> ApplicableTo<S> for Arc<E> {
type Projection = Identity;
fn into_log_entry(self) -> Arc<<S as State>::LogEntry> {
self
}
}
/// A projection from `T` to `Self::Projected`.
pub trait Projection<T>: Send + Unpin {
/// The projected/image type.
type Projected;
/// Project `val`.
fn project(val: T) -> Self::Projected;
}
/// The identity projection.
#[derive(Debug)]
pub struct Identity;
impl<T> Projection<T> for Identity {
type Projected = T;
fn project(val: T) -> Self::Projected {
val
}
}