1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
//! Property-based tests for Comonad laws.
//!
//! This module verifies that Pattern's Comonad operations satisfy the three
//! fundamental Comonad laws:
//!
//! 1. **Left Identity** (extract-extend): `extract(extend(f, p)) == f(p)`
//! 2. **Right Identity** (extend-extract): `extend(extract, p) == p`
//! 3. **Associativity**: `extend(f, extend(g, p)) == extend(f ∘ extend(g), p)`
//!
//! These laws ensure that Comonad operations behave predictably and compose correctly.
use pattern_core::Pattern;
use proptest::prelude::*;
// ============================================================================
// Arbitrary Pattern Generator
// ============================================================================
/// Generates arbitrary patterns for property-based testing.
///
/// Limits depth to avoid stack overflow and ensures reasonable sizes for testing.
fn arbitrary_pattern_i32() -> impl Strategy<Value = Pattern<i32>> {
let leaf = any::<i32>().prop_map(Pattern::point);
leaf.prop_recursive(
3, // max depth
10, // max total nodes
5, // max children per node
|inner| {
(any::<i32>(), prop::collection::vec(inner, 0..5)).prop_map(|(v, elements)| {
if elements.is_empty() {
Pattern::point(v)
} else {
Pattern::pattern(v, elements)
}
})
},
)
}
/// Generates arbitrary patterns with string values for property-based testing.
fn arbitrary_pattern_string() -> impl Strategy<Value = Pattern<String>> {
let leaf = "[a-z]{1,3}".prop_map(Pattern::point);
leaf.prop_recursive(
3, // max depth
10, // max total nodes
5, // max children per node
|inner| {
("[a-z]{1,3}", prop::collection::vec(inner, 0..5)).prop_map(|(v, elements)| {
if elements.is_empty() {
Pattern::point(v)
} else {
Pattern::pattern(v, elements)
}
})
},
)
}
// ============================================================================
// Comonad Law 1: Left Identity (Extract-Extend)
// ============================================================================
proptest! {
/// **Law 1: Left Identity (Extract-Extend)**
///
/// `extract(extend(f, p)) == f(p)`
///
/// This law states that extracting the root value after extending with function `f`
/// gives the same result as applying `f` directly to the pattern.
///
/// This is true by definition of `extend`: the root value of `extend(f, p)` is `f(p)`.
#[test]
fn comonad_law_left_identity_depth(p in arbitrary_pattern_i32()) {
// Function: compute depth of subpattern
let f = |subp: &Pattern<i32>| subp.depth();
// Left side: extract after extend
let left = *p.extend(&f).extract();
// Right side: apply function directly
let right = f(&p);
prop_assert_eq!(left, right, "Left identity failed for depth function");
}
/// Test left identity with size function.
#[test]
fn comonad_law_left_identity_size(p in arbitrary_pattern_i32()) {
let f = |subp: &Pattern<i32>| subp.size();
let left = *p.extend(&f).extract();
let right = f(&p);
prop_assert_eq!(left, right, "Left identity failed for size function");
}
/// Test left identity with value extraction (identity function on values).
#[test]
fn comonad_law_left_identity_value(p in arbitrary_pattern_i32()) {
let f = |subp: &Pattern<i32>| *subp.extract();
let left = *p.extend(&f).extract();
let right = f(&p);
prop_assert_eq!(left, right, "Left identity failed for value extraction");
}
}
// ============================================================================
// Comonad Law 2: Right Identity (Extend-Extract)
// ============================================================================
proptest! {
/// **Law 2: Right Identity (Extend-Extract)**
///
/// `extend(extract, p) == p`
///
/// This law states that extending with `extract` returns the pattern unchanged.
///
/// At each position, we're computing the decoration using `extract`, which returns
/// the existing decoration, so the result should be identical to the input.
#[test]
fn comonad_law_right_identity(p in arbitrary_pattern_i32()) {
// Extend with extract (should return p unchanged)
let result = p.extend(&|subp: &Pattern<i32>| *subp.extract());
prop_assert_eq!(result, p, "Right identity failed: extend(extract) should equal identity");
}
/// Test right identity with string patterns.
#[test]
fn comonad_law_right_identity_string(p in arbitrary_pattern_string()) {
let result = p.extend(&|subp: &Pattern<String>| subp.extract().clone());
prop_assert_eq!(result, p, "Right identity failed for string patterns");
}
}
// ============================================================================
// Comonad Law 3: Associativity
// ============================================================================
proptest! {
/// **Law 3: Associativity**
///
/// `extend(f, extend(g, p)) == extend(f ∘ extend(g), p)`
///
/// This law states that extending twice in sequence is the same as extending once
/// with the composed function.
///
/// The order of applying context-aware transformations shouldn't change the result.
#[test]
fn comonad_law_associativity_depth_size(p in arbitrary_pattern_i32()) {
// Two functions to compose
let g = |subp: &Pattern<i32>| subp.size();
let f = |subp: &Pattern<usize>| subp.depth();
// Left side: extend twice in sequence
let left = p.extend(&g).extend(&f);
// Right side: extend once with composed function
let right = p.extend(&|subp: &Pattern<i32>| {
let temp = subp.extend(&g);
f(&temp)
});
prop_assert_eq!(left, right, "Associativity failed for depth ∘ size");
}
/// Test associativity with size and value extraction.
#[test]
fn comonad_law_associativity_size_value(p in arbitrary_pattern_i32()) {
let g = |subp: &Pattern<i32>| *subp.extract();
let f = |subp: &Pattern<i32>| subp.size();
let left = p.extend(&g).extend(&f);
let right = p.extend(&|subp: &Pattern<i32>| {
let temp = subp.extend(&g);
f(&temp)
});
prop_assert_eq!(left, right, "Associativity failed for size ∘ extract");
}
/// Test associativity with depth twice (depth of depths).
#[test]
fn comonad_law_associativity_depth_depth(p in arbitrary_pattern_i32()) {
let g = |subp: &Pattern<i32>| subp.depth();
let f = |subp: &Pattern<usize>| subp.depth();
let left = p.extend(&g).extend(&f);
let right = p.extend(&|subp: &Pattern<i32>| {
let temp = subp.extend(&g);
f(&temp)
});
prop_assert_eq!(left, right, "Associativity failed for depth ∘ depth");
}
}
// ============================================================================
// Structure Preservation Property
// ============================================================================
proptest! {
/// **Property: Structure Preservation**
///
/// `extend` must preserve the pattern structure:
/// - Same number of nodes
/// - Same tree shape (nesting structure)
/// - Only values change
#[test]
fn extend_preserves_structure(p in arbitrary_pattern_i32()) {
let f = |subp: &Pattern<i32>| subp.depth();
let result = p.extend(&f);
// Same number of nodes
prop_assert_eq!(result.size(), p.size(), "extend changed node count");
// Same number of elements at root
prop_assert_eq!(result.elements().len(), p.elements().len(), "extend changed element count");
// Same maximum depth
prop_assert_eq!(result.depth(), p.depth(), "extend changed depth");
}
/// Test that depth_at preserves structure.
#[test]
fn depth_at_preserves_structure(p in arbitrary_pattern_i32()) {
let result = p.depth_at();
prop_assert_eq!(result.size(), p.size());
prop_assert_eq!(result.depth(), p.depth());
}
/// Test that size_at preserves structure.
#[test]
fn size_at_preserves_structure(p in arbitrary_pattern_i32()) {
let result = p.size_at();
prop_assert_eq!(result.size(), p.size());
prop_assert_eq!(result.depth(), p.depth());
}
}
// ============================================================================
// Edge Cases
// ============================================================================
#[test]
fn atomic_pattern_laws() {
// Test laws on atomic patterns
let p = Pattern::point(42);
// Left identity
let f = |subp: &Pattern<i32>| subp.depth();
assert_eq!(*p.extend(&f).extract(), f(&p));
// Right identity
let result = p.extend(&|subp: &Pattern<i32>| *subp.extract());
assert_eq!(result, p);
}
#[test]
fn deeply_nested_pattern() {
// Build a deeply nested pattern manually (depth 4)
let p = Pattern::pattern(
1,
vec![Pattern::pattern(
2,
vec![Pattern::pattern(
3,
vec![Pattern::pattern(4, vec![Pattern::point(5)])],
)],
)],
);
// Verify depth is correct (depth 4: levels 1->2->3->4, with 5 being atomic/depth 0)
assert_eq!(p.depth(), 4);
// Test laws
let f = |subp: &Pattern<i32>| subp.size();
assert_eq!(*p.extend(&f).extract(), f(&p));
let result = p.extend(&|subp: &Pattern<i32>| *subp.extract());
assert_eq!(result, p);
}
#[test]
fn pattern_with_many_children() {
// Pattern with many children at root
let p = Pattern::pattern(0, (1..=10).map(Pattern::point).collect());
assert_eq!(p.elements().len(), 10);
assert_eq!(p.size(), 11);
// Test laws
let f = |subp: &Pattern<i32>| subp.elements().len();
assert_eq!(*p.extend(&f).extract(), f(&p));
}