chore: remove radicle-crdt
The `radicle-crdt` crate is no longer used by any of the other crates. Choose to remove the crate so that it no longer needs to build and test as part of our workspace.
This commit is contained in:
parent
78ba263d0a
commit
42285e71cf
|
|
@ -2455,20 +2455,6 @@ dependencies = [
|
||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "radicle-crdt"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"fastrand",
|
|
||||||
"num-traits",
|
|
||||||
"qcheck",
|
|
||||||
"qcheck-macros",
|
|
||||||
"radicle-crypto",
|
|
||||||
"serde",
|
|
||||||
"tempfile",
|
|
||||||
"thiserror 1.0.69",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "radicle-crypto"
|
name = "radicle-crypto"
|
||||||
version = "0.12.0"
|
version = "0.12.0"
|
||||||
|
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "radicle-crdt"
|
|
||||||
version = "0.1.0"
|
|
||||||
license.workspace = true
|
|
||||||
edition.workspace = true
|
|
||||||
rust-version.workspace = true
|
|
||||||
|
|
||||||
[features]
|
|
||||||
test = ["fastrand", "qcheck"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
fastrand = { workspace = true, optional = true }
|
|
||||||
num-traits = { version = "0.2.15", default-features = false, features = ["std"] }
|
|
||||||
qcheck = { workspace = true, optional = true }
|
|
||||||
radicle-crypto = { workspace = true }
|
|
||||||
serde = { workspace = true }
|
|
||||||
thiserror = { workspace = true }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
fastrand = { workspace = true }
|
|
||||||
qcheck = { workspace = true }
|
|
||||||
qcheck-macros = { workspace = true }
|
|
||||||
radicle-crypto = { workspace = true, features = ["test"] }
|
|
||||||
tempfile = { workspace = true }
|
|
||||||
|
|
@ -1,155 +0,0 @@
|
||||||
use std::fmt;
|
|
||||||
use std::str::FromStr;
|
|
||||||
use std::time::SystemTime;
|
|
||||||
use std::time::UNIX_EPOCH;
|
|
||||||
|
|
||||||
use num_traits::Bounded;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
use crate::ord::Max;
|
|
||||||
use crate::Semilattice as _;
|
|
||||||
|
|
||||||
/// Lamport clock.
|
|
||||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
|
||||||
#[serde(transparent)]
|
|
||||||
pub struct Lamport {
|
|
||||||
counter: Max<u64>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Lamport {
|
|
||||||
/// Return the clock value.
|
|
||||||
pub fn get(&self) -> u64 {
|
|
||||||
*self.counter.get()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The initial value of the clock.
|
|
||||||
pub fn initial() -> Self {
|
|
||||||
Self::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Increment clock and return new value.
|
|
||||||
/// Must be called before sending a message.
|
|
||||||
pub fn tick(&mut self) -> Self {
|
|
||||||
self.counter.incr();
|
|
||||||
*self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Merge clock with another clock, and increment value.
|
|
||||||
/// Must be called whenever a message is received.
|
|
||||||
pub fn merge(&mut self, other: Self) -> Self {
|
|
||||||
self.counter.merge(other.counter);
|
|
||||||
self.tick()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reset clock to default state.
|
|
||||||
pub fn reset(&mut self) {
|
|
||||||
self.counter = Max::default();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Lamport {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(f, "{}", self.get())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<u64> for Lamport {
|
|
||||||
fn from(counter: u64) -> Self {
|
|
||||||
Self {
|
|
||||||
counter: Max::from(counter),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Lamport> for u64 {
|
|
||||||
fn from(arg: Lamport) -> Self {
|
|
||||||
arg.get()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Error decoding an operation from an entry.
|
|
||||||
#[derive(Error, Debug)]
|
|
||||||
pub enum LamportError {
|
|
||||||
#[error("invalid lamport clock value")]
|
|
||||||
Invalid,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for Lamport {
|
|
||||||
type Err = LamportError;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
Self::try_from(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<&str> for Lamport {
|
|
||||||
type Error = LamportError;
|
|
||||||
|
|
||||||
fn try_from(s: &str) -> Result<Self, Self::Error> {
|
|
||||||
let v = s.parse::<u64>().map_err(|_| LamportError::Invalid)?;
|
|
||||||
Ok(v.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Bounded for Lamport {
|
|
||||||
fn min_value() -> Self {
|
|
||||||
Self::from(u64::MIN)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn max_value() -> Self {
|
|
||||||
Self::from(u64::MAX)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Physical clock. Tracks real-time by the second.
|
|
||||||
#[derive(Debug, Default, Copy, Clone, PartialOrd, PartialEq, Ord, Eq, Serialize, Deserialize)]
|
|
||||||
#[serde(transparent)]
|
|
||||||
pub struct Physical {
|
|
||||||
seconds: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Physical {
|
|
||||||
pub fn new(seconds: u64) -> Self {
|
|
||||||
Self { seconds }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn now() -> Self {
|
|
||||||
#[allow(clippy::unwrap_used)] // Safe because Unix was already invented!
|
|
||||||
let duration = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
|
|
||||||
|
|
||||||
Self {
|
|
||||||
seconds: duration.as_secs(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_secs(&self) -> u64 {
|
|
||||||
self.seconds
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<u64> for Physical {
|
|
||||||
fn from(seconds: u64) -> Self {
|
|
||||||
Self { seconds }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::ops::Add<u64> for Physical {
|
|
||||||
type Output = Self;
|
|
||||||
|
|
||||||
fn add(self, rhs: u64) -> Self::Output {
|
|
||||||
Self {
|
|
||||||
seconds: self.seconds + rhs,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Bounded for Physical {
|
|
||||||
fn min_value() -> Self {
|
|
||||||
Self { seconds: u64::MIN }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn max_value() -> Self {
|
|
||||||
Self { seconds: u64::MAX }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,118 +0,0 @@
|
||||||
use std::collections::btree_map::{Entry, IntoIter, IntoKeys};
|
|
||||||
use std::collections::BTreeMap;
|
|
||||||
use std::ops::Deref;
|
|
||||||
|
|
||||||
use crate::Semilattice;
|
|
||||||
|
|
||||||
/// Grow-only map.
|
|
||||||
///
|
|
||||||
/// Conflicting elements are merged via the [`Semilattice`] instance.
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
pub struct GMap<K, V> {
|
|
||||||
inner: BTreeMap<K, V>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K: Ord, V: Semilattice> GMap<K, V> {
|
|
||||||
pub fn singleton(key: K, value: V) -> Self {
|
|
||||||
Self {
|
|
||||||
inner: BTreeMap::from_iter([(key, value)]),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_mut(&mut self, key: &K) -> Option<&mut V> {
|
|
||||||
self.inner.get_mut(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn insert(&mut self, key: K, value: V) {
|
|
||||||
match self.inner.entry(key) {
|
|
||||||
Entry::Occupied(mut e) => {
|
|
||||||
e.get_mut().merge(value);
|
|
||||||
}
|
|
||||||
Entry::Vacant(e) => {
|
|
||||||
e.insert(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K, V> GMap<K, V> {
|
|
||||||
pub fn into_keys(self) -> IntoKeys<K, V> {
|
|
||||||
self.inner.into_keys()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K: Ord, V: Semilattice> FromIterator<(K, V)> for GMap<K, V> {
|
|
||||||
fn from_iter<I: IntoIterator<Item = (K, V)>>(iter: I) -> Self {
|
|
||||||
let mut map = GMap::default();
|
|
||||||
for (k, v) in iter.into_iter() {
|
|
||||||
map.insert(k, v);
|
|
||||||
}
|
|
||||||
map
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K: Ord, V: Semilattice> Extend<(K, V)> for GMap<K, V> {
|
|
||||||
fn extend<I: IntoIterator<Item = (K, V)>>(&mut self, iter: I) {
|
|
||||||
for (k, v) in iter.into_iter() {
|
|
||||||
self.insert(k, v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K, V> IntoIterator for GMap<K, V> {
|
|
||||||
type Item = (K, V);
|
|
||||||
type IntoIter = IntoIter<K, V>;
|
|
||||||
|
|
||||||
fn into_iter(self) -> Self::IntoIter {
|
|
||||||
self.inner.into_iter()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K, V> Default for GMap<K, V> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
inner: BTreeMap::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K: Ord, V: Semilattice> Semilattice for GMap<K, V> {
|
|
||||||
fn merge(&mut self, other: Self) {
|
|
||||||
for (k, v) in other.into_iter() {
|
|
||||||
self.insert(k, v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K, V> Deref for GMap<K, V> {
|
|
||||||
type Target = BTreeMap<K, V>;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.inner
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use qcheck_macros::quickcheck;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
use crate::ord::Max;
|
|
||||||
|
|
||||||
#[quickcheck]
|
|
||||||
fn prop_semilattice(
|
|
||||||
a: Vec<(u8, Max<u8>)>,
|
|
||||||
b: Vec<(u8, Max<u8>)>,
|
|
||||||
c: Vec<(u8, Max<u8>)>,
|
|
||||||
mix: Vec<(u8, Max<u8>)>,
|
|
||||||
) {
|
|
||||||
let mut a = GMap::from_iter(a);
|
|
||||||
let mut b = GMap::from_iter(b);
|
|
||||||
let c = GMap::from_iter(c);
|
|
||||||
|
|
||||||
a.extend(mix.clone());
|
|
||||||
b.extend(mix);
|
|
||||||
|
|
||||||
crate::test::assert_laws(&a, &b, &c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,97 +0,0 @@
|
||||||
use std::collections::btree_map::{IntoKeys, Keys};
|
|
||||||
use std::ops::Deref;
|
|
||||||
|
|
||||||
use crate::GMap;
|
|
||||||
use crate::Semilattice;
|
|
||||||
|
|
||||||
/// Grow-only set.
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
pub struct GSet<K> {
|
|
||||||
inner: GMap<K, ()>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K: Ord> GSet<K> {
|
|
||||||
pub fn singleton(key: K) -> Self {
|
|
||||||
Self {
|
|
||||||
inner: GMap::from_iter([(key, ())]),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn insert(&mut self, key: K) {
|
|
||||||
self.inner.insert(key, ());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn iter(&self) -> Keys<'_, K, ()> {
|
|
||||||
self.inner.keys()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K: Ord> FromIterator<K> for GSet<K> {
|
|
||||||
fn from_iter<I: IntoIterator<Item = K>>(iter: I) -> Self {
|
|
||||||
let mut map = GSet::default();
|
|
||||||
for k in iter.into_iter() {
|
|
||||||
map.insert(k);
|
|
||||||
}
|
|
||||||
map
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K: Ord> Extend<K> for GSet<K> {
|
|
||||||
fn extend<I: IntoIterator<Item = K>>(&mut self, iter: I) {
|
|
||||||
for k in iter.into_iter() {
|
|
||||||
self.insert(k);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K> IntoIterator for GSet<K> {
|
|
||||||
type Item = K;
|
|
||||||
type IntoIter = IntoKeys<K, ()>;
|
|
||||||
|
|
||||||
fn into_iter(self) -> Self::IntoIter {
|
|
||||||
self.inner.into_keys()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K> Default for GSet<K> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
inner: GMap::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K: Ord> Semilattice for GSet<K> {
|
|
||||||
fn merge(&mut self, other: Self) {
|
|
||||||
for k in other.into_iter() {
|
|
||||||
self.insert(k);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K> Deref for GSet<K> {
|
|
||||||
type Target = GMap<K, ()>;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.inner
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use qcheck_macros::quickcheck;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[quickcheck]
|
|
||||||
fn prop_semilattice(a: Vec<u8>, b: Vec<u8>, c: Vec<u8>, mix: Vec<u8>) {
|
|
||||||
let mut a = GSet::from_iter(a);
|
|
||||||
let mut b = GSet::from_iter(b);
|
|
||||||
let c = GSet::from_iter(c);
|
|
||||||
|
|
||||||
a.extend(mix.clone());
|
|
||||||
b.extend(mix);
|
|
||||||
|
|
||||||
crate::test::assert_laws(&a, &b, &c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
use crate::Semilattice;
|
|
||||||
|
|
||||||
/// A [`Semilattice`] that panics when attempting to merge inequal elements.
|
|
||||||
/// Use this for types that will never merge.
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub struct Immutable<T>(pub T);
|
|
||||||
|
|
||||||
impl<T> Immutable<T> {
|
|
||||||
/// Create a new immutable object.
|
|
||||||
pub fn new(inner: T) -> Self {
|
|
||||||
Self(inner)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> std::ops::Deref for Immutable<T> {
|
|
||||||
type Target = T;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: PartialEq> Semilattice for Immutable<T> {
|
|
||||||
fn merge(&mut self, other: Self) {
|
|
||||||
if self.0 != other.0 {
|
|
||||||
panic!("Immutable::merge: Cannot merge inequal objects");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[should_panic]
|
|
||||||
fn test_merge_inequal() {
|
|
||||||
let mut a = Immutable::new(0);
|
|
||||||
let b = Immutable::new(1);
|
|
||||||
|
|
||||||
a.merge(b);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_merge_equal() {
|
|
||||||
let mut a = Immutable::new(1);
|
|
||||||
let b = Immutable::new(1);
|
|
||||||
|
|
||||||
a.merge(b);
|
|
||||||
assert_eq!(a, b);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,121 +0,0 @@
|
||||||
#![allow(clippy::collapsible_if)]
|
|
||||||
#![allow(clippy::bool_assert_comparison)]
|
|
||||||
#![allow(clippy::collapsible_else_if)]
|
|
||||||
#![allow(clippy::type_complexity)]
|
|
||||||
pub mod clock;
|
|
||||||
pub mod gmap;
|
|
||||||
pub mod gset;
|
|
||||||
pub mod immutable;
|
|
||||||
pub mod lwwmap;
|
|
||||||
pub mod lwwreg;
|
|
||||||
pub mod lwwset;
|
|
||||||
pub mod ord;
|
|
||||||
pub mod redactable;
|
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test"))]
|
|
||||||
pub mod test;
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
pub use clock::Lamport;
|
|
||||||
pub use gmap::GMap;
|
|
||||||
pub use gset::GSet;
|
|
||||||
pub use immutable::Immutable;
|
|
||||||
pub use lwwmap::LWWMap;
|
|
||||||
pub use lwwreg::LWWReg;
|
|
||||||
pub use lwwset::LWWSet;
|
|
||||||
pub use ord::{Max, Min};
|
|
||||||
pub use redactable::Redactable;
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
/// A join-semilattice.
|
|
||||||
pub trait Semilattice: Sized {
|
|
||||||
/// Merge an other semilattice into this one.
|
|
||||||
///
|
|
||||||
/// This operation should obbey the semilattice laws and should thus be idempotent,
|
|
||||||
/// associative and commutative.
|
|
||||||
fn merge(&mut self, other: Self);
|
|
||||||
|
|
||||||
/// Like [`Semilattice::merge`] but takes and returns a new semilattice.
|
|
||||||
fn join(mut self, other: Self) -> Self {
|
|
||||||
self.merge(other);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Semilattice> Semilattice for Option<T> {
|
|
||||||
fn merge(&mut self, other: Self) {
|
|
||||||
match (self, other) {
|
|
||||||
(this @ None, other @ Some(_)) => {
|
|
||||||
*this = other;
|
|
||||||
}
|
|
||||||
(Some(ref mut a), Some(b)) => {
|
|
||||||
a.merge(b);
|
|
||||||
}
|
|
||||||
(Some(_), None) => {}
|
|
||||||
(None, None) => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Semilattice for () {
|
|
||||||
fn merge(&mut self, _other: Self) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Semilattice for bool {
|
|
||||||
fn merge(&mut self, other: Self) {
|
|
||||||
match (&self, other) {
|
|
||||||
(false, true) => *self = true,
|
|
||||||
(true, false) => *self = true,
|
|
||||||
(false, false) | (true, true) => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn fold<S>(i: impl IntoIterator<Item = S>) -> S
|
|
||||||
where
|
|
||||||
S: Semilattice + Default,
|
|
||||||
{
|
|
||||||
i.into_iter().fold(S::default(), S::join)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use crate::{test, Max, Min, Semilattice};
|
|
||||||
use qcheck_macros::quickcheck;
|
|
||||||
|
|
||||||
#[quickcheck]
|
|
||||||
fn prop_option_laws(a: Max<u8>, b: Max<u8>, c: Max<u8>) {
|
|
||||||
test::assert_laws(&a, &b, &c);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[quickcheck]
|
|
||||||
fn prop_bool_laws(a: bool, b: bool, c: bool) {
|
|
||||||
test::assert_laws(&a, &b, &c);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_bool() {
|
|
||||||
assert_eq!(false.join(false), false);
|
|
||||||
assert_eq!(true.join(true), true);
|
|
||||||
assert_eq!(true.join(false), true);
|
|
||||||
assert_eq!(false.join(true), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_option() {
|
|
||||||
assert_eq!(None::<()>.join(None), None);
|
|
||||||
assert_eq!(None::<()>.join(Some(())), Some(()));
|
|
||||||
assert_eq!(Some(()).join(None), Some(()));
|
|
||||||
assert_eq!(Some(()).join(Some(())), Some(()));
|
|
||||||
assert_eq!(
|
|
||||||
Some(Max::from(0)).join(Some(Max::from(1))),
|
|
||||||
Some(Max::from(1))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
Some(Min::from(0)).join(Some(Min::from(1))),
|
|
||||||
Some(Min::from(0))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,187 +0,0 @@
|
||||||
use crate::gmap::GMap;
|
|
||||||
use crate::lwwreg::LWWReg;
|
|
||||||
use crate::{clock, Semilattice};
|
|
||||||
|
|
||||||
/// Last-Write-Wins Map.
|
|
||||||
///
|
|
||||||
/// In case a value is added and removed under a key at the same time,
|
|
||||||
/// the "add" takes precedence over the "remove".
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
pub struct LWWMap<K, V, C = clock::Lamport> {
|
|
||||||
inner: GMap<K, LWWReg<Option<V>, C>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K: Ord, V: Semilattice, C: PartialOrd + Ord> LWWMap<K, V, C> {
|
|
||||||
pub fn singleton(key: K, value: V, clock: C) -> Self {
|
|
||||||
Self {
|
|
||||||
inner: GMap::singleton(key, LWWReg::new(Some(value), clock)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get(&self, key: &K) -> Option<&V> {
|
|
||||||
let Some(value) = self.inner.get(key) else {
|
|
||||||
// If the element was never added, return nothing.
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
value.get().as_ref()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn insert(&mut self, key: K, value: V, clock: C) {
|
|
||||||
self.inner.insert(key, LWWReg::new(Some(value), clock));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remove(&mut self, key: K, clock: C) {
|
|
||||||
self.inner.insert(key, LWWReg::new(None, clock));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn contains_key(&self, key: &K) -> bool {
|
|
||||||
let Some(value) = self.inner.get(key) else {
|
|
||||||
// If the element was never added, return false.
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
value.get().is_some()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn iter(&self) -> impl Iterator<Item = (&K, &V)> {
|
|
||||||
self.inner
|
|
||||||
.iter()
|
|
||||||
.filter_map(|(k, v)| v.get().as_ref().map(|v| (k, v)))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn len(&self) -> usize {
|
|
||||||
self.iter().count()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_empty(&self) -> bool {
|
|
||||||
self.iter().next().is_none()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K, V, C> Default for LWWMap<K, V, C> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
inner: GMap::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K: Ord, V: Semilattice, C: Ord> FromIterator<(K, V, C)> for LWWMap<K, V, C> {
|
|
||||||
fn from_iter<I: IntoIterator<Item = (K, V, C)>>(iter: I) -> Self {
|
|
||||||
let mut map = LWWMap::default();
|
|
||||||
for (k, v, c) in iter.into_iter() {
|
|
||||||
map.insert(k, v, c);
|
|
||||||
}
|
|
||||||
map
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K: Ord, V: Semilattice, C: Ord> Extend<(K, V, C)> for LWWMap<K, V, C> {
|
|
||||||
fn extend<I: IntoIterator<Item = (K, V, C)>>(&mut self, iter: I) {
|
|
||||||
for (k, v, c) in iter.into_iter() {
|
|
||||||
self.insert(k, v, c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K, V, C> Semilattice for LWWMap<K, V, C>
|
|
||||||
where
|
|
||||||
K: Ord,
|
|
||||||
V: Semilattice,
|
|
||||||
C: Ord,
|
|
||||||
{
|
|
||||||
fn merge(&mut self, other: Self) {
|
|
||||||
self.inner.merge(other.inner);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use qcheck_macros::quickcheck;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
use crate::ord::Max;
|
|
||||||
|
|
||||||
#[quickcheck]
|
|
||||||
fn prop_semilattice(
|
|
||||||
a: Vec<(u8, Max<u8>, u16)>,
|
|
||||||
b: Vec<(u8, Max<u8>, u16)>,
|
|
||||||
c: Vec<(u8, Max<u8>, u16)>,
|
|
||||||
mix: Vec<(u8, Max<u8>, u16)>,
|
|
||||||
) {
|
|
||||||
let mut a = LWWMap::from_iter(a);
|
|
||||||
let mut b = LWWMap::from_iter(b);
|
|
||||||
let c = LWWMap::from_iter(c);
|
|
||||||
|
|
||||||
a.extend(mix.clone());
|
|
||||||
b.extend(mix);
|
|
||||||
|
|
||||||
crate::test::assert_laws(&a, &b, &c);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_insert() {
|
|
||||||
let mut map = LWWMap::default();
|
|
||||||
|
|
||||||
map.insert('a', Max::from(1), 0);
|
|
||||||
map.insert('b', Max::from(2), 0);
|
|
||||||
map.insert('c', Max::from(3), 0);
|
|
||||||
|
|
||||||
assert_eq!(map.get(&'a'), Some(&Max::from(1)));
|
|
||||||
assert_eq!(map.get(&'b'), Some(&Max::from(2)));
|
|
||||||
assert_eq!(map.get(&'?'), None);
|
|
||||||
|
|
||||||
let values = map.iter().collect::<Vec<(&char, &Max<u8>)>>();
|
|
||||||
assert!(values.contains(&(&'a', &Max::from(1))));
|
|
||||||
assert!(values.contains(&(&'b', &Max::from(2))));
|
|
||||||
assert!(values.contains(&(&'c', &Max::from(3))));
|
|
||||||
assert_eq!(values.len(), 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_insert_remove() {
|
|
||||||
let mut map = LWWMap::default();
|
|
||||||
|
|
||||||
map.insert('a', Max::from("alice"), 1);
|
|
||||||
assert!(map.contains_key(&'a'));
|
|
||||||
|
|
||||||
map.remove('a', 0);
|
|
||||||
assert!(map.contains_key(&'a'));
|
|
||||||
|
|
||||||
map.remove('a', 1);
|
|
||||||
assert!(map.contains_key(&'a')); // Add takes precedence over remove.
|
|
||||||
assert!(map.iter().any(|(c, _)| *c == 'a'));
|
|
||||||
|
|
||||||
map.remove('a', 2);
|
|
||||||
assert!(!map.contains_key(&'a'));
|
|
||||||
assert!(!map.iter().any(|(c, _)| *c == 'a'));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_is_empty() {
|
|
||||||
let mut map = LWWMap::default();
|
|
||||||
assert!(map.is_empty());
|
|
||||||
|
|
||||||
map.insert('a', Max::from("alice"), 1);
|
|
||||||
assert!(!map.is_empty());
|
|
||||||
|
|
||||||
map.remove('a', 2);
|
|
||||||
assert!(map.is_empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_remove_insert() {
|
|
||||||
let mut map = LWWMap::default();
|
|
||||||
|
|
||||||
map.insert('a', Max::from("alice"), 1);
|
|
||||||
assert_eq!(map.get(&'a'), Some(&Max::from("alice")));
|
|
||||||
|
|
||||||
map.remove('a', 2);
|
|
||||||
assert!(!map.contains_key(&'a'));
|
|
||||||
|
|
||||||
map.insert('a', Max::from("alice"), 1);
|
|
||||||
assert!(!map.contains_key(&'a'));
|
|
||||||
|
|
||||||
map.insert('a', Max::from("amy"), 2);
|
|
||||||
assert_eq!(map.get(&'a'), Some(&Max::from("amy")));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,134 +0,0 @@
|
||||||
use num_traits::Bounded;
|
|
||||||
|
|
||||||
use crate::clock;
|
|
||||||
use crate::ord::Max;
|
|
||||||
use crate::Semilattice;
|
|
||||||
|
|
||||||
/// Last-Write-Wins Register.
|
|
||||||
///
|
|
||||||
/// In case of conflict, uses the [`Semilattice`] instance of `T` to merge.
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
pub struct LWWReg<T, C = clock::Lamport> {
|
|
||||||
clock: Max<C>,
|
|
||||||
value: T,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Semilattice, C: PartialOrd> LWWReg<T, C> {
|
|
||||||
pub fn initial(value: T) -> Self
|
|
||||||
where
|
|
||||||
C: Default,
|
|
||||||
{
|
|
||||||
Self {
|
|
||||||
clock: Max::from(C::default()),
|
|
||||||
value,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new(value: T, clock: C) -> Self {
|
|
||||||
Self {
|
|
||||||
clock: Max::from(clock),
|
|
||||||
value,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set(&mut self, value: impl Into<T>, clock: C) {
|
|
||||||
let clock = Max::from(clock);
|
|
||||||
let value = value.into();
|
|
||||||
|
|
||||||
if clock == self.clock {
|
|
||||||
self.value.merge(value);
|
|
||||||
} else if clock > self.clock {
|
|
||||||
self.clock.merge(clock);
|
|
||||||
self.value = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get(&self) -> &T {
|
|
||||||
&self.value
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn clock(&self) -> &Max<C> {
|
|
||||||
&self.clock
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_inner(self) -> (T, C) {
|
|
||||||
(self.value, self.clock.into_inner())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Default, C: Default + Bounded> Default for LWWReg<T, C> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
clock: Max::default(),
|
|
||||||
value: T::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, C> Semilattice for LWWReg<T, C>
|
|
||||||
where
|
|
||||||
T: Semilattice,
|
|
||||||
C: PartialOrd,
|
|
||||||
{
|
|
||||||
fn merge(&mut self, other: Self) {
|
|
||||||
self.set(other.value, other.clock.into_inner());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use qcheck_macros::quickcheck;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
use crate::Min;
|
|
||||||
|
|
||||||
#[quickcheck]
|
|
||||||
fn prop_semilattice(a: (Max<u8>, u16), b: (Max<u8>, u16), c: (Max<u8>, u16)) {
|
|
||||||
let a = LWWReg::new(a.0, a.1);
|
|
||||||
let b = LWWReg::new(b.0, b.1);
|
|
||||||
let c = LWWReg::new(c.0, c.1);
|
|
||||||
|
|
||||||
crate::test::assert_laws(&a, &b, &c);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_merge() {
|
|
||||||
let a = LWWReg::new(Max::from(0), 0);
|
|
||||||
let b = LWWReg::new(Max::from(1), 0);
|
|
||||||
|
|
||||||
assert_eq!(a.join(b).get(), &Max::from(1));
|
|
||||||
|
|
||||||
let a = LWWReg::new(Min::from(0), 0);
|
|
||||||
let b = LWWReg::new(Min::from(1), 0);
|
|
||||||
|
|
||||||
assert_eq!(a.join(b).get(), &Min::from(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_set_get() {
|
|
||||||
let mut reg = LWWReg::new(Max::from(42), 1);
|
|
||||||
assert_eq!(*reg.get(), Max::from(42));
|
|
||||||
|
|
||||||
reg.set(84, 0);
|
|
||||||
assert_eq!(*reg.get(), Max::from(42));
|
|
||||||
|
|
||||||
reg.set(84, 2);
|
|
||||||
assert_eq!(*reg.get(), Max::from(84));
|
|
||||||
|
|
||||||
// Smaller value, same clock: smaller value loses.
|
|
||||||
reg.set(42, 2);
|
|
||||||
assert_eq!(*reg.get(), Max::from(84));
|
|
||||||
|
|
||||||
// Bigger value, same clock: bigger value wins.
|
|
||||||
reg.set(168, 2);
|
|
||||||
assert_eq!(*reg.get(), Max::from(168));
|
|
||||||
|
|
||||||
// Smaller value, newer clock: smaller value wins.
|
|
||||||
reg.set(42, 3);
|
|
||||||
assert_eq!(*reg.get(), Max::from(42));
|
|
||||||
|
|
||||||
// Same value, newer clock: newer clock is set.
|
|
||||||
reg.set(42, 4);
|
|
||||||
assert_eq!(*reg.clock(), Max::from(4));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,161 +0,0 @@
|
||||||
use crate::clock;
|
|
||||||
use crate::{lwwmap::LWWMap, Semilattice};
|
|
||||||
|
|
||||||
/// Last-Write-Wins Set.
|
|
||||||
///
|
|
||||||
/// In case the same value is added and removed at the same time,
|
|
||||||
/// the "add" takes precedence over the "remove".
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
pub struct LWWSet<T, C = clock::Lamport> {
|
|
||||||
inner: LWWMap<T, (), C>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Ord, C: Ord> LWWSet<T, C> {
|
|
||||||
pub fn singleton(value: T, clock: C) -> Self {
|
|
||||||
Self {
|
|
||||||
inner: LWWMap::from_iter([(value, (), clock)]),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn insert(&mut self, value: T, clock: C) {
|
|
||||||
self.inner.insert(value, (), clock);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remove(&mut self, value: T, clock: C) {
|
|
||||||
self.inner.remove(value, clock);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn contains(&self, value: &T) -> bool {
|
|
||||||
self.inner.contains_key(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn iter(&self) -> impl Iterator<Item = &T> {
|
|
||||||
self.inner.iter().map(|(k, _)| k)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_empty(&self) -> bool {
|
|
||||||
self.inner.is_empty()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, C> Default for LWWSet<T, C> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
inner: LWWMap::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Ord, C: Ord> FromIterator<(T, C)> for LWWSet<T, C> {
|
|
||||||
fn from_iter<I: IntoIterator<Item = (T, C)>>(iter: I) -> Self {
|
|
||||||
let mut set = LWWSet::default();
|
|
||||||
for (v, c) in iter.into_iter() {
|
|
||||||
set.insert(v, c);
|
|
||||||
}
|
|
||||||
set
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Ord, C: Ord> Extend<(T, C)> for LWWSet<T, C> {
|
|
||||||
fn extend<I: IntoIterator<Item = (T, C)>>(&mut self, iter: I) {
|
|
||||||
for (v, c) in iter.into_iter() {
|
|
||||||
self.insert(v, c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, C> Semilattice for LWWSet<T, C>
|
|
||||||
where
|
|
||||||
T: Ord,
|
|
||||||
C: Ord + Default,
|
|
||||||
{
|
|
||||||
fn merge(&mut self, other: Self) {
|
|
||||||
self.inner.merge(other.inner);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use qcheck_macros::quickcheck;
|
|
||||||
|
|
||||||
#[quickcheck]
|
|
||||||
fn prop_semilattice(
|
|
||||||
a: Vec<(u8, u16)>,
|
|
||||||
b: Vec<(u8, u16)>,
|
|
||||||
c: Vec<(u8, u16)>,
|
|
||||||
mix: Vec<(u8, u16)>,
|
|
||||||
) {
|
|
||||||
let mut a = LWWSet::from_iter(a);
|
|
||||||
let mut b = LWWSet::from_iter(b);
|
|
||||||
let c = LWWSet::from_iter(c);
|
|
||||||
|
|
||||||
a.extend(mix.clone());
|
|
||||||
b.extend(mix);
|
|
||||||
|
|
||||||
crate::test::assert_laws(&a, &b, &c);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_insert() {
|
|
||||||
let mut set = LWWSet::default();
|
|
||||||
|
|
||||||
set.insert('a', 0);
|
|
||||||
set.insert('b', 0);
|
|
||||||
set.insert('c', 0);
|
|
||||||
|
|
||||||
assert!(set.contains(&'a'));
|
|
||||||
assert!(set.contains(&'b'));
|
|
||||||
assert!(!set.contains(&'?'));
|
|
||||||
|
|
||||||
let values = set.iter().cloned().collect::<Vec<_>>();
|
|
||||||
assert!(values.contains(&'a'));
|
|
||||||
assert!(values.contains(&'b'));
|
|
||||||
assert!(values.contains(&'c'));
|
|
||||||
assert_eq!(values.len(), 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_insert_remove() {
|
|
||||||
let mut set = LWWSet::default();
|
|
||||||
|
|
||||||
set.insert('a', 1);
|
|
||||||
assert!(set.contains(&'a'));
|
|
||||||
|
|
||||||
set.remove('a', 0);
|
|
||||||
assert!(set.contains(&'a'));
|
|
||||||
|
|
||||||
set.remove('a', 1);
|
|
||||||
assert!(set.contains(&'a')); // Add takes precedence over remove.
|
|
||||||
assert!(set.iter().any(|c| *c == 'a'));
|
|
||||||
|
|
||||||
set.remove('a', 2);
|
|
||||||
assert!(!set.contains(&'a'));
|
|
||||||
assert!(!set.iter().any(|c| *c == 'a'));
|
|
||||||
|
|
||||||
set.insert('b', 3);
|
|
||||||
set.remove('b', 3);
|
|
||||||
assert!(set.contains(&'b')); // Insert precedence.
|
|
||||||
|
|
||||||
set.remove('c', 3);
|
|
||||||
set.insert('c', 3);
|
|
||||||
assert!(set.contains(&'c')); // Insert precedence.
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_remove_insert() {
|
|
||||||
let mut set = LWWSet::default();
|
|
||||||
|
|
||||||
set.insert('a', 1);
|
|
||||||
assert!(set.contains(&'a'));
|
|
||||||
|
|
||||||
set.remove('a', 2);
|
|
||||||
assert!(!set.contains(&'a'));
|
|
||||||
|
|
||||||
set.insert('a', 1);
|
|
||||||
assert!(!set.contains(&'a'));
|
|
||||||
|
|
||||||
set.insert('a', 2);
|
|
||||||
assert!(set.contains(&'a'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,129 +0,0 @@
|
||||||
use std::{cmp, ops};
|
|
||||||
|
|
||||||
use num_traits::Bounded;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use crate::Semilattice;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
|
||||||
#[serde(transparent)]
|
|
||||||
pub struct Max<T>(T);
|
|
||||||
|
|
||||||
impl<T> Max<T> {
|
|
||||||
pub fn get(&self) -> &T {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_inner(self) -> T {
|
|
||||||
self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: num_traits::SaturatingAdd + num_traits::One> Max<T> {
|
|
||||||
pub fn incr(&mut self) {
|
|
||||||
self.0 = self.0.saturating_add(&T::one());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Default for Max<T>
|
|
||||||
where
|
|
||||||
T: Bounded,
|
|
||||||
{
|
|
||||||
fn default() -> Self {
|
|
||||||
Self(T::min_value())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> ops::Deref for Max<T> {
|
|
||||||
type Target = T;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> From<T> for Max<T> {
|
|
||||||
fn from(t: T) -> Self {
|
|
||||||
Self(t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: PartialOrd> Semilattice for Max<T> {
|
|
||||||
fn merge(&mut self, other: Self) {
|
|
||||||
if other.0 > self.0 {
|
|
||||||
self.0 = other.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Bounded> Bounded for Max<T> {
|
|
||||||
fn min_value() -> Self {
|
|
||||||
Self::from(T::min_value())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn max_value() -> Self {
|
|
||||||
Self::from(T::max_value())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::derive_ord_xor_partial_ord)]
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Ord, Serialize, Deserialize)]
|
|
||||||
#[serde(transparent)]
|
|
||||||
pub struct Min<T>(pub T);
|
|
||||||
|
|
||||||
impl<T> Default for Min<T>
|
|
||||||
where
|
|
||||||
T: Bounded,
|
|
||||||
{
|
|
||||||
fn default() -> Self {
|
|
||||||
Self(T::max_value())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> ops::Deref for Min<T> {
|
|
||||||
type Target = T;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> From<T> for Min<T> {
|
|
||||||
fn from(t: T) -> Self {
|
|
||||||
Self(t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> cmp::PartialOrd for Min<T>
|
|
||||||
where
|
|
||||||
T: PartialOrd,
|
|
||||||
{
|
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
|
|
||||||
other.0.partial_cmp(&self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: PartialOrd> Semilattice for Min<T> {
|
|
||||||
fn merge(&mut self, other: Self) {
|
|
||||||
if other.0 < self.0 {
|
|
||||||
self.0 = other.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test"))]
|
|
||||||
mod arbitrary {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
impl<T: qcheck::Arbitrary> qcheck::Arbitrary for Max<T> {
|
|
||||||
fn arbitrary(g: &mut qcheck::Gen) -> Self {
|
|
||||||
Self::from(T::arbitrary(g))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: qcheck::Arbitrary> qcheck::Arbitrary for Min<T> {
|
|
||||||
fn arbitrary(g: &mut qcheck::Gen) -> Self {
|
|
||||||
Self::from(T::arbitrary(g))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,111 +0,0 @@
|
||||||
use crate::Semilattice;
|
|
||||||
|
|
||||||
/// An object that can be either present or removed.
|
|
||||||
///
|
|
||||||
/// The "redacted" state is the top-most element and takes precedence
|
|
||||||
/// over other states.
|
|
||||||
///
|
|
||||||
/// There is no `Default` instance, since this is not a "bounded" semilattice.
|
|
||||||
///
|
|
||||||
/// 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(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub enum Redactable<T> {
|
|
||||||
/// When the object is present.
|
|
||||||
Present(T),
|
|
||||||
/// When the object has been removed.
|
|
||||||
Redacted,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Redactable<T> {
|
|
||||||
pub fn get(&self) -> Option<&T> {
|
|
||||||
match self {
|
|
||||||
Self::Present(val) => Some(val),
|
|
||||||
Self::Redacted => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_mut(&mut self) -> Option<&mut T> {
|
|
||||||
match self {
|
|
||||||
Self::Present(ref mut val) => Some(val),
|
|
||||||
Self::Redacted => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> From<Option<T>> for Redactable<T> {
|
|
||||||
fn from(option: Option<T>) -> Self {
|
|
||||||
match option {
|
|
||||||
Some(v) => Self::Present(v),
|
|
||||||
None => Self::Redacted,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> From<Redactable<T>> for Option<T> {
|
|
||||||
fn from(redactable: Redactable<T>) -> Self {
|
|
||||||
match redactable {
|
|
||||||
Redactable::Present(v) => Some(v),
|
|
||||||
Redactable::Redacted => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, T> From<&'a Redactable<T>> for Option<&'a T> {
|
|
||||||
fn from(redactable: &'a Redactable<T>) -> Self {
|
|
||||||
redactable.get()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: PartialEq> Semilattice for Redactable<T> {
|
|
||||||
fn merge(&mut self, other: Self) {
|
|
||||||
match (&self, other) {
|
|
||||||
(Self::Redacted, _) => {}
|
|
||||||
(Self::Present(_), Self::Redacted) => {
|
|
||||||
*self = Self::Redacted;
|
|
||||||
}
|
|
||||||
(Self::Present(a), Self::Present(b)) => {
|
|
||||||
if a != &b {
|
|
||||||
*self = Self::Redacted;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use qcheck_macros::quickcheck;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
use crate::test;
|
|
||||||
|
|
||||||
#[quickcheck]
|
|
||||||
fn prop_invariants(a: Option<u8>, b: Option<u8>, c: Option<u8>) {
|
|
||||||
let a = Redactable::from(a);
|
|
||||||
let b = Redactable::from(b);
|
|
||||||
let c = Redactable::from(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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,111 +0,0 @@
|
||||||
use std::fmt::Debug;
|
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
/// Generate test values following a weight distribution.
|
|
||||||
pub struct WeightedGenerator<'a, T, C> {
|
|
||||||
cases: Vec<Rc<dyn Fn(&mut C, fastrand::Rng) -> Option<T> + 'a>>,
|
|
||||||
rng: fastrand::Rng,
|
|
||||||
ctx: C,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, C> Iterator for WeightedGenerator<'_, T, C> {
|
|
||||||
type Item = T;
|
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
let cases = self.cases.len();
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let r = self.rng.usize(0..cases);
|
|
||||||
let g = &self.cases[r];
|
|
||||||
|
|
||||||
if let Some(val) = g(&mut self.ctx, self.rng.clone()) {
|
|
||||||
return Some(val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, T, C: Default> WeightedGenerator<'a, T, C> {
|
|
||||||
/// Create a new distribution.
|
|
||||||
pub fn new(rng: fastrand::Rng) -> Self {
|
|
||||||
Self {
|
|
||||||
cases: Vec::new(),
|
|
||||||
rng,
|
|
||||||
ctx: C::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add a new variant with a given weight and generator function.
|
|
||||||
pub fn variant(
|
|
||||||
mut self,
|
|
||||||
weight: usize,
|
|
||||||
generator: impl Fn(&mut C, fastrand::Rng) -> Option<T> + 'a,
|
|
||||||
) -> Self {
|
|
||||||
let gen = Rc::new(generator);
|
|
||||||
for _ in 0..weight {
|
|
||||||
self.cases.push(gen.clone());
|
|
||||||
}
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Assert semilattice ACI laws.
|
|
||||||
pub fn assert_laws<S: Debug + Semilattice + PartialEq + Clone>(a: &S, b: &S, c: &S) {
|
|
||||||
assert_associative(a, b, c);
|
|
||||||
assert_commutative(a, b);
|
|
||||||
assert_commutative(b, c);
|
|
||||||
assert_idempotent(a);
|
|
||||||
assert_idempotent(b);
|
|
||||||
assert_idempotent(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn assert_associative<S: Debug + Semilattice + PartialEq + Clone>(a: &S, b: &S, c: &S) {
|
|
||||||
// (a ^ b) ^ c
|
|
||||||
let s1 = a.clone().join(b.clone()).join(c.clone());
|
|
||||||
// a ^ (b ^ c)
|
|
||||||
let s2 = a.clone().join(b.clone().join(c.clone()));
|
|
||||||
// (a ^ b) ^ c = a ^ (b ^ c)
|
|
||||||
assert_eq!(s1, s2, "associativity");
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn assert_commutative<S: Debug + Semilattice + PartialEq + Clone>(a: &S, b: &S) {
|
|
||||||
// a ^ b
|
|
||||||
let s1 = a.clone().join(b.clone());
|
|
||||||
// b ^ a
|
|
||||||
let s2 = b.clone().join(a.clone());
|
|
||||||
// a ^ b = b ^ a
|
|
||||||
assert_eq!(s1, s2, "commutativity");
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn assert_idempotent<S: Debug + Semilattice + PartialEq + Clone>(a: &S) {
|
|
||||||
// a ^ a
|
|
||||||
let s1 = a.clone().join(a.clone());
|
|
||||||
// a
|
|
||||||
let s2 = a.clone();
|
|
||||||
// a ^ a = a
|
|
||||||
assert_eq!(s1, s2, "idempotence");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_generator() {
|
|
||||||
let rng = fastrand::Rng::with_seed(0);
|
|
||||||
let dist = WeightedGenerator::<char, ()>::new(rng)
|
|
||||||
.variant(1, |_, _| Some('a'))
|
|
||||||
.variant(2, |_, _| Some('b'))
|
|
||||||
.variant(4, |_, _| Some('c'))
|
|
||||||
.variant(8, |_, _| Some('d'));
|
|
||||||
|
|
||||||
let values = dist.take(1000).collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let a = values.iter().filter(|c| **c == 'a').count();
|
|
||||||
let b = values.iter().filter(|c| **c == 'b').count();
|
|
||||||
let c = values.iter().filter(|c| **c == 'c').count();
|
|
||||||
let d = values.iter().filter(|c| **c == 'd').count();
|
|
||||||
|
|
||||||
assert_eq!(a, 63);
|
|
||||||
assert_eq!(b, 151);
|
|
||||||
assert_eq!(c, 255);
|
|
||||||
assert_eq!(d, 531);
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue