Mercurial > lbo > hg > memoize
changeset 97:525fcf2058ae
Merge pull request #23 from blyxyas/master
[#18] Add custom hasher parameter
author | Lewin Bormann <lbo@spheniscida.de> |
---|---|
date | Sun, 15 Jan 2023 18:41:31 +0100 |
parents | db1c1e6b5642 (current diff) 528ce50ec228 (diff) |
children | 0444f8e3099d |
files | |
diffstat | 8 files changed, 163 insertions(+), 16 deletions(-) [+] |
line wrap: on
line diff
--- a/Cargo.toml Fri Jan 13 17:21:02 2023 +0100 +++ b/Cargo.toml Sun Jan 15 18:41:31 2023 +0100 @@ -16,9 +16,14 @@ lazy_static = "1.4" lru = { version = "0.7", optional = true } +[dev-dependencies] + +rustc-hash = "1.1.0" +ahash = "0.8.2" + [workspace] members = ["inner/"] [features] -default = [] +default = ["full"] full = ["lru", "memoize-inner/full"]
--- a/README.md Fri Jan 13 17:21:02 2023 +0100 +++ b/README.md Sun Jan 15 18:41:31 2023 +0100 @@ -118,6 +118,18 @@ The cached value will never be older than duration provided and instead recalculated on the next request. +You can also specifiy a **custom hasher**, like [AHash](https://github.com/tkaitchuck/aHash) using `CustomHasher`. + +```rust +#[memoize(CustomHasher: ahash::HashMap)] +``` + +As some hashers initializing functions other than `new()`, you can specifiy a `HasherInit` function call: + +```rust +#[memoize(CustomHasher: FxHashMap, HasherInit: FxHashMap::default())] +``` + ### Flushing If you memoize a function `f`, there will be a function called
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/examples/ahash.rs Sun Jan 15 18:41:31 2023 +0100 @@ -0,0 +1,24 @@ +use ahash::{HashMap, HashMapExt}; +use memoize::memoize; + +#[cfg(feature = "full")] +#[memoize(CustomHasher: HashMap)] +fn hello() -> bool { + println!("hello!"); + true +} + +#[cfg(feature = "full")] +fn main() { + // `hello` is only called once here. + assert!(hello()); + assert!(hello()); + memoized_flush_hello(); + // and again here. + assert!(hello()); +} + +#[cfg(not(feature = "full"))] +fn main() { + println!("Use the \"full\" feature to execute this example"); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/examples/custom_hasher.rs Sun Jan 15 18:41:31 2023 +0100 @@ -0,0 +1,32 @@ +//! Reproduces (verifies) issue #17: Panics when used on fn without args. + +use memoize::memoize; + +#[cfg(feature = "full")] +#[memoize(CustomHasher: std::collections::HashMap)] +fn hello() -> bool { + println!("hello!"); + true +} + +// ! This will panic because CustomHasher and Capacity are being used. +// #[cfg(feature = "full")] +// #[memoize(CustomHasher: std::collections::HashMap, Capacity: 3usize)] +// fn will_panic(a: u32, b: u32) -> u32 { +// a + b +// } + +#[cfg(feature = "full")] +fn main() { + // `hello` is only called once here. + assert!(hello()); + assert!(hello()); + memoized_flush_hello(); + // and again here. + assert!(hello()); +} + +#[cfg(not(feature = "full"))] +fn main() { + println!("Use the \"full\" feature to execute this example"); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/examples/fxhash.rs Sun Jan 15 18:41:31 2023 +0100 @@ -0,0 +1,24 @@ +use memoize::memoize; +use rustc_hash::FxHashMap; + +#[cfg(feature = "full")] +#[memoize(CustomHasher: FxHashMap, HasherInit: FxHashMap::default())] +fn hello() -> bool { + println!("hello!"); + true +} + +#[cfg(feature = "full")] +fn main() { + // `hello` is only called once here. + assert!(hello()); + assert!(hello()); + memoized_flush_hello(); + // and again here. + assert!(hello()); +} + +#[cfg(not(feature = "full"))] +fn main() { + println!("Use the \"full\" feature to execute this example"); +}
--- a/examples/patterns.rs Fri Jan 13 17:21:02 2023 +0100 +++ b/examples/patterns.rs Sun Jan 15 18:41:31 2023 +0100 @@ -19,7 +19,7 @@ fn main() { // `manhattan_distance` is only called once here. assert_eq!(manhattan_distance((1, 1), (1, 3)), 2); - + // Same with `get_value`. assert_eq!(get_value(OnlyOne::Value(0)), 0); }
--- a/inner/src/lib.rs Fri Jan 13 17:21:02 2023 +0100 +++ b/inner/src/lib.rs Sun Jan 15 18:41:31 2023 +0100 @@ -1,7 +1,7 @@ #![crate_type = "proc-macro"] #![allow(unused_imports)] // Spurious complaints about a required trait import. -use syn::{self, parse, parse_macro_input, spanned::Spanned, Expr, ItemFn}; +use syn::{self, parse, parse_macro_input, spanned::Spanned, Expr, ExprCall, ItemFn, Path}; use proc_macro::TokenStream; use quote::{self, ToTokens}; @@ -10,6 +10,8 @@ syn::custom_keyword!(Capacity); syn::custom_keyword!(TimeToLive); syn::custom_keyword!(SharedCache); + syn::custom_keyword!(CustomHasher); + syn::custom_keyword!(HasherInit); syn::custom_punctuation!(Colon, :); } @@ -18,6 +20,8 @@ lru_max_entries: Option<usize>, time_to_live: Option<Expr>, shared_cache: bool, + custom_hasher: Option<Path>, + custom_hasher_initializer: Option<ExprCall>, } #[derive(Clone)] @@ -25,6 +29,8 @@ LRUMaxEntries(usize), TimeToLive(Expr), SharedCache, + CustomHasher(Path), + HasherInit(ExprCall), } // To extend option parsing, add functionality here. @@ -60,6 +66,18 @@ input.parse::<kw::SharedCache>().unwrap(); return Ok(CacheOption::SharedCache); } + if la.peek(kw::CustomHasher) { + input.parse::<kw::CustomHasher>().unwrap(); + input.parse::<kw::Colon>().unwrap(); + let cap: syn::Path = input.parse().unwrap(); + return Ok(CacheOption::CustomHasher(cap)); + } + if la.peek(kw::HasherInit) { + input.parse::<kw::HasherInit>().unwrap(); + input.parse::<kw::Colon>().unwrap(); + let cap: syn::ExprCall = input.parse().unwrap(); + return Ok(CacheOption::HasherInit(cap)); + } Err(la.error()) } } @@ -74,6 +92,8 @@ match opt { CacheOption::LRUMaxEntries(cap) => opts.lru_max_entries = Some(cap), CacheOption::TimeToLive(sec) => opts.time_to_live = Some(sec), + CacheOption::CustomHasher(hasher) => opts.custom_hasher = Some(hasher), + CacheOption::HasherInit(init) => opts.custom_hasher_initializer = Some(init), CacheOption::SharedCache => opts.shared_cache = true, } } @@ -94,10 +114,17 @@ value_type: proc_macro2::TokenStream, ) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) { // This is the unbounded default. - ( - quote::quote! { std::collections::HashMap<#key_type, #value_type> }, - quote::quote! { std::collections::HashMap::new() }, - ) + if let Some(hasher) = &_options.custom_hasher { + return ( + quote::quote! { #hasher<#key_type, #value_type> }, + quote::quote! { #hasher::new() }, + ); + } else { + ( + quote::quote! { std::collections::HashMap<#key_type, #value_type> }, + quote::quote! { std::collections::HashMap::new() }, + ) + } } /// Returns names of methods as TokenStreams to insert and get (respectively) elements from a @@ -131,14 +158,35 @@ }; // This is the unbounded default. match options.lru_max_entries { - None => ( - quote::quote! { std::collections::HashMap<#key_type, #value_type> }, - quote::quote! { std::collections::HashMap::new() }, - ), - Some(cap) => ( - quote::quote! { ::memoize::lru::LruCache<#key_type, #value_type> }, - quote::quote! { ::memoize::lru::LruCache::new(#cap) }, - ), + None => { + if let Some(hasher) = &options.custom_hasher { + if let Some(hasher_init) = &options.custom_hasher_initializer { + return ( + quote::quote! { #hasher<#key_type, #value_type> }, + quote::quote! { #hasher_init }, + ); + } else { + return ( + quote::quote! { #hasher<#key_type, #value_type> }, + quote::quote! { #hasher::new() }, + ); + } + } + ( + quote::quote! { std::collections::HashMap<#key_type, #value_type> }, + quote::quote! { std::collections::HashMap::new() }, + ) + } + Some(cap) => { + if let Some(_) = &options.custom_hasher { + panic!("You can't use LRUMaxEntries and a Custom Hasher. Remove `LRUMaxEntries` from the attribute"); + } else { + ( + quote::quote! { ::memoize::lru::LruCache<#key_type, #value_type> }, + quote::quote! { ::memoize::lru::LruCache::new(#cap) }, + ) + } + } } } @@ -196,6 +244,8 @@ * no longer than duration provided and refreshed with next request. If you prefer chrono::Duration, * it can be also used: `#[memoize(TimeToLive: chrono::Duration::hours(9).to_std().unwrap()]` * + * You can also specify a custom hasher: `#[memoize(CustomHasher: ahash::HashMap)]`, as some hashers don't use a `new()` method to initialize them, you can also specifiy a `HasherInit` parameter, like this: `#[memoize(CustomHasher: FxHashMap, HasherInit: FxHashMap::default())]`, so it will initialize your `FxHashMap` with `FxHashMap::default()` insteado of `FxHashMap::new()` + * * This mechanism can, in principle, be extended (in the source code) to any other cache mechanism. * * `memoized_flush_<function name>()` allows you to clear the underlying memoization cache of a