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
    }
}