From 550ad2b55828ab85c52a146f7767f79e6ebe03ee Mon Sep 17 00:00:00 2001 From: Alexis Sellier Date: Thu, 24 Nov 2022 19:45:36 +0100 Subject: [PATCH] Make `Thread` a semilattice Signed-off-by: Alexis Sellier --- radicle-crdt/src/lib.rs | 38 +++++++++++++++++++++++++++++++++- radicle-crdt/src/redactable.rs | 29 +++++++++++++++++++++++--- radicle-crdt/src/thread.rs | 18 +++++++++++++++- 3 files changed, 80 insertions(+), 5 deletions(-) diff --git a/radicle-crdt/src/lib.rs b/radicle-crdt/src/lib.rs index 2d3afc58..6ca7bc54 100644 --- a/radicle-crdt/src/lib.rs +++ b/radicle-crdt/src/lib.rs @@ -14,6 +14,9 @@ mod test; //////////////////////////////////////////////////////////////////////////////// +use std::collections::{BTreeMap, HashMap}; +use std::hash::Hash; + /// A join-semilattice. pub trait Semilattice: Sized { /// Merge an other semilattice into this one. @@ -29,7 +32,40 @@ pub trait Semilattice: Sized { } } -/// Reduce an iterator of semilattice values to its least upper bound. +impl Semilattice for BTreeMap { + fn merge(&mut self, other: Self) { + use std::collections::btree_map::Entry; + + for (k, v) in other { + match self.entry(k) { + Entry::Occupied(mut e) => { + e.get_mut().merge(v); + } + Entry::Vacant(e) => { + e.insert(v); + } + } + } + } +} + +impl Semilattice for HashMap { + fn merge(&mut self, other: Self) { + use std::collections::hash_map::Entry; + + for (k, v) in other { + match self.entry(k) { + Entry::Occupied(mut e) => { + e.get_mut().merge(v); + } + Entry::Vacant(e) => { + e.insert(v); + } + } + } + } +} + pub fn fold(i: impl IntoIterator) -> S where S: Semilattice + Default, diff --git a/radicle-crdt/src/redactable.rs b/radicle-crdt/src/redactable.rs index de404717..46bfaadc 100644 --- a/radicle-crdt/src/redactable.rs +++ b/radicle-crdt/src/redactable.rs @@ -1,6 +1,10 @@ use crate::Semilattice; /// An object that can be either present or removed. +/// +/// Nb. The merge rules are such that if two redactables with different +/// values present are merged; the result is redacted. This is the preserve +/// the semilattice laws. #[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] pub enum Redactable { /// When the object is present. @@ -19,7 +23,7 @@ impl From> for Redactable { } } -impl Semilattice for Redactable { +impl Semilattice for Redactable { fn merge(&mut self, other: Self) { match (&self, other) { (Self::Redacted, _) => {} @@ -27,8 +31,8 @@ impl Semilattice for Redactable { *self = Self::Redacted; } (Self::Present(a), Self::Present(b)) => { - if &b > a { - *self = Self::Present(b); + if a != &b { + *self = Self::Redacted; } } } @@ -50,4 +54,23 @@ mod test { test::assert_laws(&a, &b, &c); } + + #[test] + fn test_redacted() { + let a = Redactable::Present(0); + let b = Redactable::Redacted; + + assert_eq!(a.join(b), Redactable::Redacted); + assert_eq!(b.join(a), Redactable::Redacted); + assert_eq!(a.join(a), a); + } + + #[test] + fn test_both_present() { + let a = Redactable::Present(0); + let b = Redactable::Present(1); + + assert_eq!(a.join(b), Redactable::Redacted); + assert_eq!(a.join(b), b.join(a)); + } } diff --git a/radicle-crdt/src/thread.rs b/radicle-crdt/src/thread.rs index d0b211e1..f18fac95 100644 --- a/radicle-crdt/src/thread.rs +++ b/radicle-crdt/src/thread.rs @@ -12,6 +12,7 @@ use crate::clock::LClock; use crate::lwwreg::LWWReg; use crate::lwwset::LWWSet; use crate::redactable::Redactable; +use crate::Semilattice; /// Identifies a change. pub type ChangeId = radicle::hash::Digest; @@ -85,6 +86,12 @@ impl Comment { } } +impl PartialOrd for Comment { + fn partial_cmp(&self, _other: &Self) -> Option { + None + } +} + /// An action that can be carried out in a change. #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] pub enum Action { @@ -115,6 +122,14 @@ pub struct Thread { reactions: BTreeMap>, } +impl Semilattice for Thread { + fn merge(&mut self, other: Self) { + self.comments.merge(other.comments); + self.tags.merge(other.tags); + self.reactions.merge(other.reactions); + } +} + impl Deref for Thread { type Target = BTreeMap>; @@ -329,7 +344,7 @@ mod tests { use radicle::{cob::TypeName, crypto::test::signer::MockSigner, identity::project::Identity}; use super::*; - use crate::test::WeightedGenerator; + use crate::test::{assert_laws, WeightedGenerator}; #[derive(Clone)] struct Changes { @@ -574,6 +589,7 @@ mod tests { assert_eq!(t1, t2); assert_eq!(t2, t3); + assert_laws(&t1, &t2, &t3); } #[test]