Skip to main content

mini_kanren/core/
substitution.rs

1//! Substitutions map variables to values.
2//!
3//! Variables can be atomic, composite or variables themselves.
4
5use super::value::Value;
6use crate::core::logic_variable::Var;
7use crate::core::structure::Structure;
8use std::borrow::Cow;
9use std::collections::HashMap;
10use std::fmt::Formatter;
11
12/// Mapping of variables to values.
13#[derive(Clone, PartialEq)]
14pub struct Substitution<'s> {
15    pub(crate) subs: Cow<'s, HashMap<Var, Value>>,
16}
17
18impl Default for Substitution<'_> {
19    fn default() -> Self {
20        Self::empty()
21    }
22}
23
24impl<'s> Substitution<'s> {
25    /// Initialize an empty substitution
26    pub fn empty() -> Self {
27        Substitution {
28            subs: Cow::Owned(HashMap::new()),
29        }
30    }
31
32    /// Get number of substituted variables
33    pub fn n_subs(&self) -> usize {
34        self.subs.len()
35    }
36
37    /// Recursively attempt to resolve the value of a variable.
38    ///
39    /// If the `v` is no variable or a variable that cannot be
40    /// resolved (i.e. it is not substituted), `v` is returned.
41    pub fn walk<'a>(&'a self, v: &'a Value) -> &'a Value {
42        if let Some(var) = v.try_as_var() {
43            if let Some(next) = self.subs.get(&var) {
44                return self.walk(next);
45            }
46        }
47        v
48    }
49
50    /// Attempt to resolve any variables contained in `v`.
51    pub fn walk_star(&self, v: &Value) -> Value {
52        self.walk(v).walk_star(self)
53    }
54
55    /// Extend substitution with a variable => value mapping.
56    ///
57    /// Returns `None` if the insertion would result in a
58    /// cyclic substitution.
59    pub fn extend(mut self, x: Var, v: Value) -> Option<Self> {
60        if self.occurs(&x, &v) {
61            None
62        } else {
63            self.subs.to_mut().insert(x, v);
64            Some(self)
65        }
66    }
67
68    /// Returns `true` if `v` contains a variable that is equivalent
69    /// to `x` when this substitution.
70    pub fn occurs(&self, x: &Var, v: &Value) -> bool {
71        let v = self.walk(v);
72        v.occurs(x, self)
73    }
74
75    /// Attempt to unify `Value`s `u` and `v` under this substitution.
76    pub fn unify(self, u: &Value, v: &Value) -> Option<Self> {
77        let u = self.walk(u);
78        let v = self.walk(v);
79
80        if let Some(u) = u.try_as_var() {
81            return u.unify(&v.clone(), self);
82        }
83
84        if let Some(v) = v.try_as_var() {
85            return v.unify(&u.clone(), self);
86        }
87
88        let u = u.clone();
89        let v = v.clone();
90        u.unify(&v, self)
91    }
92
93    /// Substitute all variables that remain fresh in `v` with reified variables.
94    pub fn reify_s(self, v: &Value) -> Self {
95        self.walk(v).clone().reify_s(self)
96    }
97
98    /// Replace all variables contained in `v` with their substituted
99    /// values and reify variables without substitution.
100    pub fn reify(&self, v: &Value) -> Value {
101        let v = self.walk_star(v);
102        let r = Substitution::empty().reify_s(&v);
103        r.walk_star(&v)
104    }
105}
106
107impl std::fmt::Debug for Substitution<'_> {
108    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
109        write!(f, "{{")?;
110        let mut iter = self.subs.iter();
111        if let Some((var, val)) = iter.next() {
112            write!(f, "{:?}: {:?}", var, val)?;
113        }
114        for (var, val) in iter {
115            write!(f, ", {:?}: {:?}", var, val)?;
116        }
117        write!(f, "}}")
118    }
119}
120
121/// Construct a substitution
122#[macro_export]
123macro_rules! substitution {
124    () => {Substitution{ subs: Cow::Owned(HashMap::new())}};
125
126    ($($var:ident : $val:expr),*) => {{
127        let mut subs = HashMap::new();
128        $(
129            subs.insert($var.clone(), Value::from($val.clone()));
130        )*
131        Substitution {
132            subs: Cow::Owned(subs)
133        }
134    }}
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140
141    fn walk(v: Var, s: &Substitution) -> Value {
142        s.walk(&Value::var(v)).clone()
143    }
144
145    #[test]
146    fn it_works() {
147        let v = Var::new("v");
148        let w = Var::new("w");
149        let x = Var::new("x");
150        let y = Var::new("y");
151        let z = Var::new("z");
152
153        assert_eq!(walk(z, &substitution! {z: "a", x: w, y: z}), "a");
154        assert_eq!(walk(y, &substitution! {z: "a", x: w, y: z}), "a");
155        assert_eq!(walk(x, &substitution! {z: "a", x: w, y: z}), w);
156        assert_eq!(walk(x, &substitution! {x: y, v: x, w: x}), y);
157        assert_eq!(walk(v, &substitution! {x: y, v: x, w: x}), y);
158        assert_eq!(walk(w, &substitution! {x: y, v: x, w: x}), y);
159
160        assert_eq!(
161            substitution! {x: "b", z: y, w: vec![Value::from(x), "e".into(), (z).into()]}
162                .walk_star(&w.clone().into()),
163            Value::from(vec![Value::from("b"), "e".into(), y.clone().into()])
164        );
165    }
166
167    #[test]
168    fn unify_same_var_does_not_modify_substitution() {
169        let var_as_val = Var::new("x").into();
170        let sub = Substitution::empty().unify(&var_as_val, &var_as_val);
171        assert_eq!(sub, Some(Substitution::empty()));
172    }
173
174    #[test]
175    fn unify_two_vars_extends_substitution() {
176        let x = Var::new("x");
177        let y = Var::new("y");
178        let sub = Substitution::empty().unify(&x.into(), &y.into()).unwrap();
179        let expected = Substitution::empty().extend(x, y.into()).unwrap();
180        assert_eq!(sub, expected);
181    }
182
183    #[test]
184    fn unify_value_with_var_extends_substitution() {
185        let x = Var::new("x");
186        let v = Value::new(0);
187        let sub = Substitution::empty().unify(&v, &x.into()).unwrap();
188        let expected = Substitution::empty().extend(x, v).unwrap();
189        assert_eq!(sub, expected);
190    }
191
192    #[test]
193    fn unify_same_values_does_not_modify_substitution() {
194        let sub = Substitution::empty().unify(&Value::new(42), &Value::new(42));
195        assert_eq!(sub, Some(Substitution::empty()));
196    }
197
198    #[test]
199    fn unify_different_values_fails() {
200        let sub = Substitution::empty().unify(&Value::new(1), &Value::new(2));
201        assert_eq!(sub, None);
202    }
203}