Make `Thread` a semilattice

Signed-off-by: Alexis Sellier <alexis@radicle.xyz>
This commit is contained in:
Alexis Sellier 2022-11-24 19:45:36 +01:00
parent 7e8af47f59
commit 550ad2b558
No known key found for this signature in database
3 changed files with 80 additions and 5 deletions

View File

@ -14,6 +14,9 @@ mod test;
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
use std::collections::{BTreeMap, HashMap};
use std::hash::Hash;
/// A join-semilattice. /// A join-semilattice.
pub trait Semilattice: Sized { pub trait Semilattice: Sized {
/// Merge an other semilattice into this one. /// 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<K: Ord, V: Semilattice> Semilattice for BTreeMap<K, V> {
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<K: Hash + PartialEq + Eq, V: Semilattice> Semilattice for HashMap<K, V> {
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<S>(i: impl IntoIterator<Item = S>) -> S pub fn fold<S>(i: impl IntoIterator<Item = S>) -> S
where where
S: Semilattice + Default, S: Semilattice + Default,

View File

@ -1,6 +1,10 @@
use crate::Semilattice; use crate::Semilattice;
/// An object that can be either present or removed. /// 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)] #[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
pub enum Redactable<T> { pub enum Redactable<T> {
/// When the object is present. /// When the object is present.
@ -19,7 +23,7 @@ impl<T> From<Option<T>> for Redactable<T> {
} }
} }
impl<T: PartialOrd> Semilattice for Redactable<T> { impl<T: PartialEq> Semilattice for Redactable<T> {
fn merge(&mut self, other: Self) { fn merge(&mut self, other: Self) {
match (&self, other) { match (&self, other) {
(Self::Redacted, _) => {} (Self::Redacted, _) => {}
@ -27,8 +31,8 @@ impl<T: PartialOrd> Semilattice for Redactable<T> {
*self = Self::Redacted; *self = Self::Redacted;
} }
(Self::Present(a), Self::Present(b)) => { (Self::Present(a), Self::Present(b)) => {
if &b > a { if a != &b {
*self = Self::Present(b); *self = Self::Redacted;
} }
} }
} }
@ -50,4 +54,23 @@ mod test {
test::assert_laws(&a, &b, &c); 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));
}
} }

View File

@ -12,6 +12,7 @@ use crate::clock::LClock;
use crate::lwwreg::LWWReg; use crate::lwwreg::LWWReg;
use crate::lwwset::LWWSet; use crate::lwwset::LWWSet;
use crate::redactable::Redactable; use crate::redactable::Redactable;
use crate::Semilattice;
/// Identifies a change. /// Identifies a change.
pub type ChangeId = radicle::hash::Digest; pub type ChangeId = radicle::hash::Digest;
@ -85,6 +86,12 @@ impl Comment {
} }
} }
impl PartialOrd for Comment {
fn partial_cmp(&self, _other: &Self) -> Option<std::cmp::Ordering> {
None
}
}
/// An action that can be carried out in a change. /// An action that can be carried out in a change.
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
pub enum Action { pub enum Action {
@ -115,6 +122,14 @@ pub struct Thread {
reactions: BTreeMap<ChangeId, LWWSet<(ActorId, Reaction), Timestamp>>, reactions: BTreeMap<ChangeId, LWWSet<(ActorId, Reaction), Timestamp>>,
} }
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 { impl Deref for Thread {
type Target = BTreeMap<ChangeId, Redactable<Comment>>; type Target = BTreeMap<ChangeId, Redactable<Comment>>;
@ -329,7 +344,7 @@ mod tests {
use radicle::{cob::TypeName, crypto::test::signer::MockSigner, identity::project::Identity}; use radicle::{cob::TypeName, crypto::test::signer::MockSigner, identity::project::Identity};
use super::*; use super::*;
use crate::test::WeightedGenerator; use crate::test::{assert_laws, WeightedGenerator};
#[derive(Clone)] #[derive(Clone)]
struct Changes<const N: usize> { struct Changes<const N: usize> {
@ -574,6 +589,7 @@ mod tests {
assert_eq!(t1, t2); assert_eq!(t1, t2);
assert_eq!(t2, t3); assert_eq!(t2, t3);
assert_laws(&t1, &t2, &t3);
} }
#[test] #[test]