Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "hashconsing"
version = "1.6.0"
version = "1.7.0"
authors = ["Adrien Champion <adrien.champion@email.com>"]
description = "A hash consing library."
documentation = "https://docs.rs/hashconsing"
Expand Down
67 changes: 48 additions & 19 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@
#![deny(warnings)]

use std::{
borrow::Borrow,
cmp::{Eq, Ord, Ordering, PartialEq, PartialOrd},
collections::{hash_map::RandomState, HashMap},
fmt,
Expand Down Expand Up @@ -452,7 +453,7 @@ impl<T> Ord for WHConsed<T> {
}

/// The consign storing the actual hash consed elements as `HConsed`s.
pub struct HConsign<T: Hash + Eq + Clone, S = RandomState> {
pub struct HConsign<T: Hash + Eq, S = RandomState> {
/// The actual hash consing table.
table: HashMap<T, WHConsed<T>, S>,
/// Counter for uids.
Expand Down Expand Up @@ -526,7 +527,7 @@ impl<T: Hash + Eq + Clone, S> HConsign<T, S> {
}
}

impl<T: Hash + Eq + Clone, S: BuildHasher> HConsign<T, S> {
impl<T: Hash + Eq, S: BuildHasher> HConsign<T, S> {
/// Creates an empty consign with a custom hash
#[inline]
pub fn with_hasher(build_hasher: S) -> Self {
Expand Down Expand Up @@ -564,16 +565,19 @@ impl<T: Hash + Eq + Clone, S: BuildHasher> HConsign<T, S> {

/// Attempts to retrieve an *upgradable* value from the map.
#[inline]
fn get(&self, key: &T) -> Option<HConsed<T>> {
if let Some(old) = self.table.get(key) {
fn get<U: Eq + Hash + PartialEq<T>>(&self, key: &U) -> Option<HConsed<T>>
where
T: Borrow<U>,
{
if let Some(old) = self.table.get::<U>(key) {
old.to_hconsed()
} else {
None
}
}
}

impl<T: Hash + Eq + Clone, S> fmt::Display for HConsign<T, S>
impl<T: Hash + Eq, S> fmt::Display for HConsign<T, S>
where
T: Hash + fmt::Display,
{
Expand All @@ -598,10 +602,15 @@ pub trait HashConsign<T: Hash>: 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<T>, bool);
fn mk_is_new<S: Eq + HashIntern<T> + PartialEq<T> + Hash>(self, elm: S) -> (HConsed<T>, bool)
where
T: Borrow<S>;

/// Creates a HConsed element.
fn mk(self, elm: T) -> HConsed<T> {
fn mk<S: Eq + HashIntern<T> + PartialEq<T> + Hash>(self, elm: S) -> HConsed<T>
where
T: Borrow<S>
{
self.mk_is_new(elm).0
}

Expand All @@ -620,22 +629,26 @@ pub trait HashConsign<T: Hash>: Sized {
/// Reserves capacity for at least `additional` more elements.
fn reserve(self, additional: usize);
}
impl<'a, T: Hash + Eq + Clone, S: BuildHasher> HashConsign<T> for &'a mut HConsign<T, S> {
fn mk_is_new(self, elm: T) -> (HConsed<T>, bool) {

impl<'a, T: Hash + Eq, S: BuildHasher> HashConsign<T> for &'a mut HConsign<T, S> {
fn mk_is_new<U: HashIntern<T> + Eq + PartialEq<T> + Hash>(self, elm: U) -> (HConsed<T>, bool)
where
T: Borrow<U>,
{
// 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::<U>(&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)
}
Expand Down Expand Up @@ -696,13 +709,15 @@ macro_rules! get {
impl<'a, T: Hash + Eq + Clone> HashConsign<T> for &'a RwLock<HConsign<T>> {
/// If the element is already in the consign, only read access will be
/// requested.
fn mk_is_new(self, elm: T) -> (HConsed<T>, bool) {
// Request read and check if element already exists.
fn mk_is_new<U: HashIntern<T> + Eq + PartialEq<T> + Hash>(self, elm: U) -> (HConsed<T>, bool)
where
T: Borrow<U>, // 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);
}
};
Expand All @@ -711,19 +726,19 @@ impl<'a, T: Hash + Eq + Clone> HashConsign<T> for &'a RwLock<HConsign<T>> {

// 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)
}
Expand All @@ -741,3 +756,17 @@ impl<'a, T: Hash + Eq + Clone> HashConsign<T> for &'a RwLock<HConsign<T>> {
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<T> {
fn intern(&self) -> T;
}

impl<T: Clone> HashIntern<T> for T {
fn intern(&self) -> T {
self.clone()
}
}
1 change: 1 addition & 0 deletions src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@

mod basic;
mod collect;
mod string;
130 changes: 130 additions & 0 deletions src/test/string.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
//! Some basic tests.

use std::fmt;

use crate::*;

type Term<S> = HConsed<ActualTerm<S>>;

type HTerm = HConsed<ActualTerm<String>>;

#[derive(Hash, Clone, PartialEq, Eq)]
enum ActualTerm<S>
{
Var(S),
Lam(Term<String>),
App(Term<String>, Term<String>),
}

impl <S> fmt::Display for ActualTerm<S>
where S: Deref<Target=str> {
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<S: Deref<Target = str>>(&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<ActualTerm<String>> {
fn var<S: Deref<Target = str>>(&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<ActualTerm<String>> for ActualTerm<&str> {
fn intern(&self) -> ActualTerm<String> {
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<Term<String>, _> = HConMap::with_capacity(100);
let mut set: HConSet<Term<String>> = 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))
}
}