use std::cmp::Ord;
use std::collections::HashMap;
use std::hash::Hash;
use std::marker::PhantomData;
pub trait Reducer<T>: Send + Sync + 'static {
fn reduce(&self, current: T, update: T) -> T;
}
#[derive(Clone, Copy, Debug, Default)]
pub struct Replace;
impl<T> Reducer<T> for Replace
where
T: Send + Sync + 'static,
{
fn reduce(&self, _current: T, update: T) -> T {
update
}
}
#[derive(Clone, Copy, Debug)]
pub struct Append<U>(PhantomData<fn() -> U>);
impl<U> Default for Append<U> {
fn default() -> Self {
Self::new()
}
}
impl<U> Append<U> {
#[must_use]
pub const fn new() -> Self {
Self(PhantomData)
}
}
impl<U> Reducer<Vec<U>> for Append<U>
where
U: Send + Sync + 'static,
{
fn reduce(&self, mut current: Vec<U>, mut update: Vec<U>) -> Vec<U> {
current.append(&mut update);
current
}
}
#[derive(Clone, Copy, Debug)]
pub struct MergeMap<K, V>(PhantomData<fn() -> (K, V)>);
impl<K, V> Default for MergeMap<K, V> {
fn default() -> Self {
Self::new()
}
}
impl<K, V> MergeMap<K, V> {
#[must_use]
pub const fn new() -> Self {
Self(PhantomData)
}
}
impl<K, V> Reducer<HashMap<K, V>> for MergeMap<K, V>
where
K: Eq + Hash + Send + Sync + 'static,
V: Send + Sync + 'static,
{
fn reduce(&self, mut current: HashMap<K, V>, update: HashMap<K, V>) -> HashMap<K, V> {
for (k, v) in update {
current.insert(k, v);
}
current
}
}
#[derive(Clone, Copy, Debug)]
pub struct Max<T>(PhantomData<fn() -> T>);
impl<T> Default for Max<T> {
fn default() -> Self {
Self::new()
}
}
impl<T> Max<T> {
#[must_use]
pub const fn new() -> Self {
Self(PhantomData)
}
}
impl<T> Reducer<T> for Max<T>
where
T: Ord + Send + Sync + 'static,
{
fn reduce(&self, current: T, update: T) -> T {
std::cmp::max(current, update)
}
}
#[derive(Clone, Debug)]
pub struct Annotated<T, R>
where
R: Reducer<T>,
{
pub value: T,
reducer: R,
}
impl<T, R> Default for Annotated<T, R>
where
T: Default,
R: Reducer<T> + Default,
{
fn default() -> Self {
Self {
value: T::default(),
reducer: R::default(),
}
}
}
impl<T, R> Annotated<T, R>
where
R: Reducer<T>,
{
pub const fn new(value: T, reducer: R) -> Self {
Self { value, reducer }
}
pub const fn reducer(&self) -> &R {
&self.reducer
}
pub fn into_value(self) -> T {
self.value
}
pub fn reduce_in_place(&mut self, update: T)
where
T: Default,
{
let current = std::mem::take(&mut self.value);
self.value = self.reducer.reduce(current, update);
}
#[must_use]
pub fn reduced(self, update: T) -> Self
where
R: Clone,
{
let merged = self.reducer.reduce(self.value, update);
Self {
value: merged,
reducer: self.reducer,
}
}
#[must_use]
pub fn merge(self, other: Self) -> Self {
let merged = self.reducer.reduce(self.value, other.value);
Self {
value: merged,
reducer: self.reducer,
}
}
}
pub trait StateMerge: Sized {
type Contribution: Default + Send + Sync + 'static;
#[must_use]
fn merge(self, update: Self) -> Self;
#[must_use]
fn merge_contribution(self, contribution: Self::Contribution) -> Self;
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn replace_returns_update() {
let r = Replace;
assert_eq!(r.reduce(1u32, 7), 7);
}
#[test]
fn append_concatenates_vecs() {
let r = Append::<u32>::new();
assert_eq!(r.reduce(vec![1, 2], vec![3, 4]), vec![1, 2, 3, 4]);
}
#[test]
fn append_handles_empty_inputs() {
let r = Append::<u32>::new();
assert_eq!(r.reduce(Vec::new(), vec![1]), vec![1]);
assert_eq!(r.reduce(vec![1], Vec::new()), vec![1]);
assert!(r.reduce(Vec::<u32>::new(), Vec::new()).is_empty());
}
#[test]
fn merge_map_is_right_biased() {
let r = MergeMap::<&str, i32>::new();
let mut current = HashMap::new();
current.insert("a", 1);
current.insert("b", 2);
let mut update = HashMap::new();
update.insert("b", 20);
update.insert("c", 3);
let merged = r.reduce(current, update);
assert_eq!(merged.get("a"), Some(&1));
assert_eq!(merged.get("b"), Some(&20)); assert_eq!(merged.get("c"), Some(&3));
}
#[test]
fn max_keeps_larger() {
let r = Max::<i32>::new();
assert_eq!(r.reduce(3, 5), 5);
assert_eq!(r.reduce(10, 5), 10);
assert_eq!(r.reduce(-1, -3), -1);
}
#[test]
fn annotated_reduced_returns_merged() {
let a = Annotated::new(vec![1, 2], Append::<u32>::new());
let b = a.reduced(vec![3]);
assert_eq!(b.value, vec![1, 2, 3]);
}
#[test]
fn annotated_reduce_in_place_updates_value() {
let mut a = Annotated::new(vec![1, 2], Append::<u32>::new());
a.reduce_in_place(vec![3, 4]);
assert_eq!(a.value, vec![1, 2, 3, 4]);
}
#[test]
fn annotated_into_value_unwraps() {
let a = Annotated::new(42_i32, Replace);
assert_eq!(a.into_value(), 42);
}
#[test]
fn annotated_merge_combines_two_annotated_values() {
let left = Annotated::new(vec![1u32, 2], Append::<u32>::new());
let right = Annotated::new(vec![3u32, 4], Append::<u32>::new());
let merged = left.merge(right);
assert_eq!(merged.value, vec![1, 2, 3, 4]);
}
#[test]
fn annotated_merge_respects_reducer_kind() {
let left = Annotated::new(7_i32, Max::<i32>::new());
let right = Annotated::new(4_i32, Max::<i32>::new());
assert_eq!(left.merge(right).value, 7);
}
#[test]
fn state_merge_can_be_implemented_manually() {
struct WithInvariant {
log: Annotated<Vec<u32>, Append<u32>>,
tag: String,
}
#[derive(Default)]
struct WithInvariantContribution {
log: Option<Annotated<Vec<u32>, Append<u32>>>,
tag: Option<String>,
}
impl StateMerge for WithInvariant {
type Contribution = WithInvariantContribution;
fn merge(self, update: Self) -> Self {
Self {
log: self.log.merge(update.log),
tag: update.tag,
}
}
fn merge_contribution(self, c: Self::Contribution) -> Self {
Self {
log: match c.log {
Some(v) => self.log.merge(v),
None => self.log,
},
tag: c.tag.unwrap_or(self.tag),
}
}
}
let merged = WithInvariant {
log: Annotated::new(vec![1, 2], Append::new()),
tag: "old".into(),
}
.merge(WithInvariant {
log: Annotated::new(vec![3], Append::new()),
tag: "new".into(),
});
assert_eq!(merged.log.value, vec![1, 2, 3]);
assert_eq!(merged.tag, "new");
let merged2 = WithInvariant {
log: Annotated::new(vec![10], Append::new()),
tag: "keep".into(),
}
.merge_contribution(WithInvariantContribution {
log: Some(Annotated::new(vec![20], Append::new())),
tag: None,
});
assert_eq!(merged2.log.value, vec![10, 20]);
assert_eq!(merged2.tag, "keep");
}
#[test]
fn reducer_object_is_dyn_safe() {
let r: Box<dyn Reducer<Vec<i32>>> = Box::new(Append::<i32>::new());
assert_eq!(r.reduce(vec![1], vec![2]), vec![1, 2]);
}
}