diff --git a/Cargo.toml b/Cargo.toml index 739885b..a387056 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "hashconsing" -version = "1.6.0" +version = "1.7.0" authors = ["Adrien Champion "] description = "A hash consing library." documentation = "https://docs.rs/hashconsing" diff --git a/src/lib.rs b/src/lib.rs index c00789c..e4d0025 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -216,6 +216,7 @@ #![deny(warnings)] use std::{ + borrow::Borrow, cmp::{Eq, Ord, Ordering, PartialEq, PartialOrd}, collections::{hash_map::RandomState, HashMap}, fmt, @@ -452,7 +453,7 @@ impl Ord for WHConsed { } /// The consign storing the actual hash consed elements as `HConsed`s. -pub struct HConsign { +pub struct HConsign { /// The actual hash consing table. table: HashMap, S>, /// Counter for uids. @@ -526,7 +527,7 @@ impl HConsign { } } -impl HConsign { +impl HConsign { /// Creates an empty consign with a custom hash #[inline] pub fn with_hasher(build_hasher: S) -> Self { @@ -564,8 +565,11 @@ impl HConsign { /// Attempts to retrieve an *upgradable* value from the map. #[inline] - fn get(&self, key: &T) -> Option> { - if let Some(old) = self.table.get(key) { + fn get>(&self, key: &U) -> Option> + where + T: Borrow, + { + if let Some(old) = self.table.get::(key) { old.to_hconsed() } else { None @@ -573,7 +577,7 @@ impl HConsign { } } -impl fmt::Display for HConsign +impl fmt::Display for HConsign where T: Hash + fmt::Display, { @@ -598,10 +602,15 @@ pub trait HashConsign: Sized { /// - was not in the consign at all, or /// - was in the consign but it is not referenced (weak ref cannot be /// upgraded.) - fn mk_is_new(self, elm: T) -> (HConsed, bool); + fn mk_is_new + PartialEq + Hash>(self, elm: S) -> (HConsed, bool) + where + T: Borrow; /// Creates a HConsed element. - fn mk(self, elm: T) -> HConsed { + fn mk + PartialEq + Hash>(self, elm: S) -> HConsed + where + T: Borrow + { self.mk_is_new(elm).0 } @@ -620,22 +629,26 @@ pub trait HashConsign: Sized { /// Reserves capacity for at least `additional` more elements. fn reserve(self, additional: usize); } -impl<'a, T: Hash + Eq + Clone, S: BuildHasher> HashConsign for &'a mut HConsign { - fn mk_is_new(self, elm: T) -> (HConsed, bool) { + +impl<'a, T: Hash + Eq, S: BuildHasher> HashConsign for &'a mut HConsign { + fn mk_is_new + Eq + PartialEq + Hash>(self, elm: U) -> (HConsed, bool) + where + T: Borrow, + { // If the element is known and upgradable return it. - if let Some(hconsed) = self.get(&elm) { - debug_assert!(*hconsed.elm == elm); + if let Some(hconsed) = self.get::(&elm) { + debug_assert!(elm == *hconsed.elm); return (hconsed, false); } // Otherwise build hconsed version. let hconsed = HConsed { - elm: Arc::new(elm.clone()), + elm: Arc::new(elm.intern()), uid: self.count, }; // Increment uid count. self.count += 1; // ...add weak version to the table... - self.insert(elm, hconsed.to_weak()); + self.insert(elm.intern(), hconsed.to_weak()); // ...and return consed version. (hconsed, true) } @@ -696,13 +709,15 @@ macro_rules! get { impl<'a, T: Hash + Eq + Clone> HashConsign for &'a RwLock> { /// If the element is already in the consign, only read access will be /// requested. - fn mk_is_new(self, elm: T) -> (HConsed, bool) { - // Request read and check if element already exists. + fn mk_is_new + Eq + PartialEq + Hash>(self, elm: U) -> (HConsed, bool) + where + T: Borrow, // Request read and check if element already exists. + { { let slf = get!(read on self); // If the element is known and upgradable return it. if let Some(hconsed) = slf.get(&elm) { - debug_assert!(*hconsed.elm == elm); + debug_assert!(elm == *hconsed.elm); return (hconsed, false); } }; @@ -711,19 +726,19 @@ impl<'a, T: Hash + Eq + Clone> HashConsign for &'a RwLock> { // Someone might have inserted since we checked, check again. if let Some(hconsed) = slf.get(&elm) { - debug_assert!(*hconsed.elm == elm); + debug_assert!(elm == *hconsed.elm); return (hconsed, false); } // Otherwise build hconsed version. let hconsed = HConsed { - elm: Arc::new(elm.clone()), + elm: Arc::new(elm.intern()), uid: slf.count, }; // Increment uid count. slf.count += 1; // ...add weak version to the table... - slf.insert(elm, hconsed.to_weak()); + slf.insert(elm.intern(), hconsed.to_weak()); // ...and return consed version. (hconsed, true) } @@ -741,3 +756,17 @@ impl<'a, T: Hash + Eq + Clone> HashConsign for &'a RwLock> { get!(write on self).reserve(additional) } } + +/// Abstracts conversion and cloning for `hashconsing` +/// +/// `HashIntern` abstracts conversion and cloning, allowing minimal-copy implementations of HConsed +/// types. +pub trait HashIntern { + fn intern(&self) -> T; +} + +impl HashIntern for T { + fn intern(&self) -> T { + self.clone() + } +} diff --git a/src/test.rs b/src/test.rs index 3c387b7..8f14fd8 100644 --- a/src/test.rs +++ b/src/test.rs @@ -2,3 +2,4 @@ mod basic; mod collect; +mod string; \ No newline at end of file diff --git a/src/test/string.rs b/src/test/string.rs new file mode 100644 index 0000000..76f7252 --- /dev/null +++ b/src/test/string.rs @@ -0,0 +1,130 @@ +//! Some basic tests. + +use std::fmt; + +use crate::*; + +type Term = HConsed>; + +type HTerm = HConsed>; + +#[derive(Hash, Clone, PartialEq, Eq)] +enum ActualTerm +{ + Var(S), + Lam(Term), + App(Term, Term), +} + +impl fmt::Display for ActualTerm +where S: Deref { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Var(i) => write!(fmt, "v{}", i as &str), + Self::Lam(t) => write!(fmt, "({})", t.get()), + Self::App(u, v) => write!(fmt, "{}.{}", u.get(), v.get()), + } + } +} + +trait TermFactory { + fn var>(&mut self, v: S) -> HTerm; + fn lam(&mut self, t: HTerm) -> HTerm; + fn app(&mut self, u: HTerm, v: HTerm) -> HTerm; +} + +impl TermFactory for HConsign> { + fn var>(&mut self, v: S) -> HTerm { + self.mk(ActualTerm::Var(v.to_string())) + } + fn lam(&mut self, t: HTerm) -> HTerm { + self.mk(ActualTerm::Lam(t)) + } + fn app(&mut self, u: HTerm, v: HTerm) -> HTerm{ + self.mk(ActualTerm::App(u, v)) + } +} + +impl HashIntern> for ActualTerm<&str> { + fn intern(&self) -> ActualTerm { + match self { + ActualTerm::Var(v) => ActualTerm::Var(v.to_string()), + ActualTerm::Lam(t) => ActualTerm::Lam(t.clone()), + ActualTerm::App(u, v) => ActualTerm::App(u.clone(), v.clone()), + } + } +} + +#[test] +#[allow(deprecated)] +fn run() { + use coll::{HConMap, HConSet}; + + let mut consign= HConsign::empty(); + assert_eq!(consign.len(), 0); + + let mut map: HConMap, _> = HConMap::with_capacity(100); + let mut set: HConSet> = HConSet::with_capacity(100); + + let (v1, v1_name) = (consign.var("0"), "v1"); + println!("creating {v1}"); + assert_eq!(consign.len(), 1); + let prev = map.insert(v1.clone(), v1_name); + assert_eq!(prev, None); + let is_new = set.insert(v1.clone()); + assert!(is_new); + + let (v2, v2_name) = (consign.var("3"), "v2"); + println!("creating {v2}"); + assert_eq!(consign.len(), 2); + assert_ne!(v1.uid(), v2.uid()); + let prev = map.insert(v2.clone(), v2_name); + assert_eq!(prev, None); + let is_new = set.insert(v2.clone()); + assert!(is_new); + + let (lam, lam_name) = (consign.lam(v2.clone()), "lam"); + println!("creating {lam}"); + assert_eq!(consign.len(), 3); + assert_ne!(v1.uid(), lam.uid()); + assert_ne!(v2.uid(), lam.uid()); + let prev = map.insert(lam.clone(), lam_name); + assert_eq!(prev, None); + let is_new = set.insert(lam.clone()); + assert!(is_new); + + let (v3, v3_name) = (consign.var("3"), "v3"); + println!("creating {v3}"); + assert_eq!(consign.len(), 3); + assert_eq!(v2.uid(), v3.uid()); + let prev = map.insert(v3.clone(), v3_name); + assert_eq!(prev, Some(v2_name)); + let is_new = set.insert(v3.clone()); + assert!(!is_new); + + let (lam2, lam2_name) = (consign.lam(v3), "lam2"); + println!("creating {lam2}"); + assert_eq!(consign.len(), 3); + assert_eq!(lam.uid(), lam2.uid()); + let prev = map.insert(lam2.clone(), lam2_name); + assert_eq!(prev, Some(lam_name)); + let is_new = set.insert(lam2.clone()); + assert!(!is_new); + + let (app, app_name) = (consign.app(lam2, v1), "app"); + println!("creating {app}"); + assert_eq!(consign.len(), 4); + let prev = map.insert(app.clone(), app_name); + assert_eq!(prev, None); + let is_new = set.insert(app); + assert!(is_new); + + for term in &set { + assert!(map.contains_key(term)) + } + for (term, val) in &map { + println!("looking for `{val}`"); + assert!(set.contains(term)) + } +} +