Date: 2025-11-11 (Updated: 2025-11-12) Status: ✅ IMPLEMENTED (Option 1) Scope: Transducer API for substitution policy support
After thorough analysis in OPTION1_ANALYSIS.md, we implemented Option 1 with excellent results:
See OPTION1_IMPLEMENTATION.md for complete implementation details.
After successfully threading the SubstitutionPolicy parameter through all transition functions, we need to decide how to expose this functionality to users through the Transducer API.
pub struct Transducer<D: Dictionary, P: SubstitutionPolicy = Unrestricted> {
dictionary: D,
algorithm: Algorithm,
policy: P,
}
impl<D: Dictionary> Transducer<D, Unrestricted> {
pub fn new(dictionary: D, algorithm: Algorithm) -> Self { ... }
}
impl<D: Dictionary, P: SubstitutionPolicy> Transducer<D, P> {
pub fn with_policy(dictionary: D, algorithm: Algorithm, policy: P) -> Self { ... }
pub fn query(&self, term: &str, max_distance: usize) -> QueryIterator<...> { ... }
}
Pros:
transducer.query(...)Cons:
impl<D: Dictionary> Transducer<D> blocks become impl<D: Dictionary, P: SubstitutionPolicy> Transducer<D, P>pub struct Transducer<D: Dictionary> {
dictionary: D,
algorithm: Algorithm,
}
impl<D: Dictionary> Transducer<D> {
// Existing methods unchanged - use Unrestricted internally
pub fn query(&self, term: &str, max_distance: usize) -> QueryIterator<...> { ... }
// NEW: Policy-aware methods for future use
// (Deferred to when we actually implement policy-based matching)
// pub fn query_with_policy<P: SubstitutionPolicy>(..., policy: P) -> ... { ... }
}
Pros:
Unrestricted_with_policy() methods laterCons:
pub struct Transducer<D: Dictionary> { ... }
pub struct PolicyTransducer<D: Dictionary, P: SubstitutionPolicy> {
transducer: Transducer<D>,
policy: P,
}
impl<D: Dictionary> Transducer<D> {
pub fn with_policy<P: SubstitutionPolicy>(self, policy: P) -> PolicyTransducer<D, P> { ... }
}
Pros:
Cons:
Chosen: 2025-11-12
Rationale:
Unrestricted is a ZST (0 bytes)HashMap<K, V, S = RandomState>Implementation:
pub struct Transducer<D: Dictionary, P: SubstitutionPolicy = Unrestricted> {
dictionary: D,
algorithm: Algorithm,
policy: P,
}
// Backward-compatible constructors
impl<D: Dictionary> Transducer<D, Unrestricted> {
pub fn new(dictionary: D, algorithm: Algorithm) -> Self { ... }
pub fn standard(dictionary: D) -> Self { ... }
pub fn with_transposition(dictionary: D) -> Self { ... }
pub fn with_merge_split(dictionary: D) -> Self { ... }
}
// Generic methods (work with any policy)
impl<D: Dictionary, P: SubstitutionPolicy> Transducer<D, P> {
pub fn with_policy(dictionary: D, algorithm: Algorithm, policy: P) -> Self { ... }
pub fn query(&self, term: &str, max_distance: usize) -> QueryIterator<...> { ... }
// ... all query methods
}
Results:
Why NOT Chosen:
_with_policy() method variants (14+ methods)Note: This was initially chosen in the 2025-11-11 version of this document, but after analyzing Option 1's feasibility (see OPTION1_ANALYSIS.md), we discovered it IS backward compatible due to default type parameters.
The policy parameter is threaded through transition functions in preparation for future policy-based matching. Currently:
Unrestrictedcharacteristic_vector still does exact matching onlyWhen we implement actual policy-based matching in transition logic:
impl<D: Dictionary> Transducer<D> {
/// Query with custom substitution policy
///
/// # Example
/// ```
/// use liblevenshtein::prelude::*;
///
/// let dict = DynamicDawg::from_terms(vec!["cat", "bat"]);
/// // Custom policy: allow 'c' ↔ 'k' substitution
/// let mut policy_set = SubstitutionSet::new();
/// policy_set.allow('c', 'k');
/// let policy = Restricted::new(&policy_set);
/// let transducer = Transducer::with_policy(dict, Algorithm::Standard, policy);
///
/// // "kat" will match "cat" with distance 1 (c↔k allowed)
/// // but "bat" won't match (b↔k not allowed)
/// let results: Vec<_> = transducer.query("kat", 1).collect();
/// ```
pub fn with_policy(dictionary: D, algorithm: Algorithm, policy: P) -> Self {
Self {
dictionary,
algorithm,
policy,
}
}
pub fn query(&self, term: &str, max_distance: usize) -> QueryIterator<D::Node, String, P> {
QueryIterator::with_policy(
self.dictionary.root(),
term.to_owned(),
max_distance,
self.algorithm,
self.policy,
)
}
}
If we decide users need Transducer-level policy storage:
Version 0.7.0 (Current):
Transducer<D> - no policy supportVersion 0.8.0 (Hypothetical):
query_with_policy() methodsVersion 0.9.0 (Hypothetical):
Transducer::with_policy() builderTransducer<D> for backward compatibilityVersion 1.0.0 (Hypothetical):
Transducer<D, P = Unrestricted> if widely usedFinal Decision: Implement Option 1 - Generic Transducer<D, P = Unrestricted> ✅
Reasoning:
Implementation Status: ✅ COMPLETE (2025-11-12)
Next Steps:
Status: Phase 3 complete. Infrastructure in place and API exposed for future policy-based matching.
docs/development/RESTRICTED_SUBSTITUTIONS_PLAN.md - 30-day implementation timelineCan you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |