Make `Thread` a semilattice
Signed-off-by: Alexis Sellier <alexis@radicle.xyz>
This commit is contained in:
parent
7e8af47f59
commit
550ad2b558
|
|
@ -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<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
|
||||
where
|
||||
S: Semilattice + Default,
|
||||
|
|
|
|||
|
|
@ -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<T> {
|
||||
/// 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) {
|
||||
match (&self, other) {
|
||||
(Self::Redacted, _) => {}
|
||||
|
|
@ -27,8 +31,8 @@ impl<T: PartialOrd> Semilattice for Redactable<T> {
|
|||
*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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<std::cmp::Ordering> {
|
||||
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<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 {
|
||||
type Target = BTreeMap<ChangeId, Redactable<Comment>>;
|
||||
|
||||
|
|
@ -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<const N: usize> {
|
||||
|
|
@ -574,6 +589,7 @@ mod tests {
|
|||
|
||||
assert_eq!(t1, t2);
|
||||
assert_eq!(t2, t3);
|
||||
assert_laws(&t1, &t2, &t3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
Loading…
Reference in New Issue