Skip to main content

pattern_core/pattern/
comonad_helpers.rs

1//! Helper functions demonstrating practical applications of Comonad operations.
2//!
3//! This module provides position-aware helper functions that compute structural
4//! metadata at every position in a pattern:
5//!
6//! - `depth_at()`: Decorate each position with its depth (maximum nesting level)
7//! - `size_at()`: Decorate each position with its subtree size (total node count)
8//! - `indices_at()`: Decorate each position with its path from root (sequence of indices)
9//!
10//! These helpers demonstrate how `extend` enables natural expression of context-aware
11//! computation, where functions have access to the full subpattern at each position.
12
13use crate::Pattern;
14
15impl<V> Pattern<V> {
16    /// Decorates each position with its depth (maximum nesting level).
17    ///
18    /// This uses `extend` to compute the depth at every position in the pattern.
19    /// Depth is defined as:
20    /// - Atomic pattern (no elements): depth 0
21    /// - Pattern with elements: depth = 1 + max(child depths)
22    ///
23    /// # Returns
24    ///
25    /// A pattern where each position's value is the depth of that subpattern.
26    ///
27    /// # Complexity
28    ///
29    /// Time: O(n), Space: O(n)
30    ///
31    /// # Examples
32    ///
33    /// ```
34    /// use pattern_core::Pattern;
35    ///
36    /// let p = Pattern::point("x");
37    /// assert_eq!(p.depth_at().extract(), &0);
38    ///
39    /// let p = Pattern::pattern("root", vec![
40    ///     Pattern::pattern("a", vec![Pattern::point("x")]),
41    ///     Pattern::point("b")
42    /// ]);
43    /// let depths = p.depth_at();
44    /// assert_eq!(depths.extract(), &2); // root has depth 2
45    /// assert_eq!(depths.elements()[0].extract(), &1); // "a" has depth 1
46    /// assert_eq!(depths.elements()[1].extract(), &0); // "b" has depth 0
47    /// ```
48    pub fn depth_at(&self) -> Pattern<usize> {
49        self.extend(&|subpattern| subpattern.depth())
50    }
51
52    /// Decorates each position with the total node count of its subtree.
53    ///
54    /// This uses `extend` to compute the size at every position in the pattern.
55    /// Size is defined as: 1 (self) + sum of child sizes.
56    ///
57    /// # Returns
58    ///
59    /// A pattern where each position's value is the size of that subpattern.
60    ///
61    /// # Complexity
62    ///
63    /// Time: O(n), Space: O(n)
64    ///
65    /// # Examples
66    ///
67    /// ```
68    /// use pattern_core::Pattern;
69    ///
70    /// let p = Pattern::point("x");
71    /// assert_eq!(p.size_at().extract(), &1);
72    ///
73    /// let p = Pattern::pattern("root", vec![
74    ///     Pattern::point("a"),
75    ///     Pattern::point("b")
76    /// ]);
77    /// let sizes = p.size_at();
78    /// assert_eq!(sizes.extract(), &3); // root + 2 children
79    /// assert_eq!(sizes.elements()[0].extract(), &1);
80    /// assert_eq!(sizes.elements()[1].extract(), &1);
81    /// ```
82    pub fn size_at(&self) -> Pattern<usize> {
83        self.extend(&|subpattern| subpattern.size())
84    }
85
86    /// Decorates each position with its path from root (sequence of element indices).
87    ///
88    /// Unlike `depth_at` and `size_at`, this cannot use `extend` because it requires
89    /// tracking the path during traversal. The function passed to `extend` only sees
90    /// the local subpattern, not the path from the root.
91    ///
92    /// Path representation:
93    /// - Root: empty vector `[]`
94    /// - Child at index i: parent_path + `[i]`
95    ///
96    /// # Returns
97    ///
98    /// A pattern where each position's value is its path from root.
99    ///
100    /// # Complexity
101    ///
102    /// Time: O(n), Space: O(n * depth) due to path vectors
103    ///
104    /// # Examples
105    ///
106    /// ```
107    /// use pattern_core::Pattern;
108    ///
109    /// let p = Pattern::point("x");
110    /// let paths = p.indices_at();
111    /// let expected: Vec<usize> = vec![];
112    /// assert_eq!(paths.extract(), &expected);
113    ///
114    /// let p = Pattern::pattern("root", vec![
115    ///     Pattern::pattern("a", vec![Pattern::point("x")]),
116    ///     Pattern::point("b")
117    /// ]);
118    /// let paths = p.indices_at();
119    /// let expected: Vec<usize> = vec![];
120    /// assert_eq!(paths.extract(), &expected); // root path
121    /// assert_eq!(paths.elements()[0].extract(), &vec![0]); // first child path
122    /// assert_eq!(paths.elements()[0].elements()[0].extract(), &vec![0, 0]); // nested child path
123    /// assert_eq!(paths.elements()[1].extract(), &vec![1]); // second child path
124    /// ```
125    pub fn indices_at(&self) -> Pattern<Vec<usize>> {
126        fn go<V>(path: Vec<usize>, pattern: &Pattern<V>) -> Pattern<Vec<usize>> {
127            Pattern {
128                value: path.clone(),
129                elements: pattern
130                    .elements()
131                    .iter()
132                    .enumerate()
133                    .map(|(i, elem)| {
134                        let mut new_path = path.clone();
135                        new_path.push(i);
136                        go(new_path, elem)
137                    })
138                    .collect(),
139            }
140        }
141        go(vec![], self)
142    }
143}
144
145#[cfg(test)]
146mod tests {
147    use super::*;
148
149    #[test]
150    fn depth_at_atomic_pattern() {
151        let p = Pattern::point("x");
152        let depths = p.depth_at();
153        assert_eq!(depths.extract(), &0);
154    }
155
156    #[test]
157    fn depth_at_with_children() {
158        let p = Pattern::pattern("root", vec![Pattern::point("a"), Pattern::point("b")]);
159        let depths = p.depth_at();
160        assert_eq!(depths.extract(), &1); // has children (even though atomic), so depth 1
161    }
162
163    #[test]
164    fn depth_at_nested() {
165        let p = Pattern::pattern(
166            "root",
167            vec![Pattern::pattern("a", vec![Pattern::point("x")])],
168        );
169        let depths = p.depth_at();
170        assert_eq!(depths.extract(), &2); // depth through "a" to "x"
171        assert_eq!(depths.elements()[0].extract(), &1);
172    }
173
174    #[test]
175    fn size_at_atomic_pattern() {
176        let p = Pattern::point("x");
177        let sizes = p.size_at();
178        assert_eq!(sizes.extract(), &1);
179    }
180
181    #[test]
182    fn size_at_with_children() {
183        let p = Pattern::pattern(
184            "root",
185            vec![
186                Pattern::point("a"),
187                Pattern::point("b"),
188                Pattern::point("c"),
189            ],
190        );
191        let sizes = p.size_at();
192        assert_eq!(sizes.extract(), &4); // 1 root + 3 children
193        assert_eq!(sizes.elements()[0].extract(), &1);
194    }
195
196    #[test]
197    fn indices_at_atomic_pattern() {
198        let p = Pattern::point("x");
199        let paths = p.indices_at();
200        assert_eq!(paths.extract(), &Vec::<usize>::new());
201    }
202
203    #[test]
204    fn indices_at_with_children() {
205        let p = Pattern::pattern("root", vec![Pattern::point("a"), Pattern::point("b")]);
206        let paths = p.indices_at();
207        assert_eq!(paths.extract(), &Vec::<usize>::new());
208        assert_eq!(paths.elements()[0].extract(), &vec![0]);
209        assert_eq!(paths.elements()[1].extract(), &vec![1]);
210    }
211
212    #[test]
213    fn indices_at_nested() {
214        let p = Pattern::pattern(
215            "root",
216            vec![
217                Pattern::pattern("a", vec![Pattern::point("x")]),
218                Pattern::point("b"),
219            ],
220        );
221        let paths = p.indices_at();
222        assert_eq!(paths.extract(), &Vec::<usize>::new());
223        assert_eq!(paths.elements()[0].extract(), &vec![0]);
224        assert_eq!(paths.elements()[0].elements()[0].extract(), &vec![0, 0]);
225        assert_eq!(paths.elements()[1].extract(), &vec![1]);
226    }
227}