algebra_core/
fix.rs

1//! # Fixed-point types and recursion schemes
2//!
3//! This module provides the building blocks for recursion schemes in
4//! Rust:
5//!
6//! - [`TypeApp`]: Higher-kinded type encoding
7//! - [`Functor`]: Functor on one type parameter
8//! - [`Fix`]: Least fixed point (μF)
9//! - [`fold`]: Catamorphism / F-algebra eliminator
10//!
11//! # Example: Linked List
12//!
13//! ```rust
14//! use algebra_core::fix::{TypeApp, Functor, Fix, fold};
15//!
16//! // Base functor for List<A>: ListF<A, X> = Nil | Cons(A, X)
17//! enum ListF<A, X> {
18//!     Nil,
19//!     Cons(A, X),
20//! }
21//!
22//! // Type-level tag for ListF<A, _>
23//! struct ListTag<A>(std::marker::PhantomData<A>);
24//!
25//! impl<A> TypeApp for ListTag<A> {
26//!     type Applied<X> = ListF<A, X>;
27//! }
28//!
29//! impl<A: Clone> Functor for ListTag<A> {
30//!     fn fmap<X, Y, G>(fx: ListF<A, X>, mut g: G) -> ListF<A, Y>
31//!     where
32//!         G: FnMut(X) -> Y,
33//!     {
34//!         match fx {
35//!             ListF::Nil => ListF::Nil,
36//!             ListF::Cons(a, x) => ListF::Cons(a.clone(), g(x)),
37//!         }
38//!     }
39//! }
40//!
41//! // List<A> is the fixed point of ListF<A, _>
42//! type List<A> = Fix<ListTag<A>>;
43//!
44//! // Smart constructors
45//! fn nil<A: Clone>() -> List<A> {
46//!     Fix::new(ListF::Nil)
47//! }
48//!
49//! fn cons<A: Clone>(head: A, tail: List<A>) -> List<A> {
50//!     Fix::new(ListF::Cons(head, tail))
51//! }
52//!
53//! // Build a list: [1, 2, 3]
54//! let list = cons(1, cons(2, cons(3, nil())));
55//!
56//! // Compute length via fold
57//! let length: usize = fold(list, |layer| match layer {
58//!     ListF::Nil => 0,
59//!     ListF::Cons(_, n) => n + 1,
60//! });
61//! assert_eq!(length, 3);
62//! ```
63
64use std::fmt::Debug;
65
66/// A **type constructor** encoding via associated types.
67///
68/// Rust lacks higher-kinded types, so we encode `F : Type → Type` as:
69/// - A marker type `F` (the "tag")
70/// - An associated type `Applied<X>` representing `F(X)`
71///
72/// # Example
73///
74/// ```rust
75/// use algebra_core::fix::TypeApp;
76///
77/// // Option as a type constructor
78/// struct OptionTag;
79///
80/// impl TypeApp for OptionTag {
81///     type Applied<X> = Option<X>;
82/// }
83///
84/// // Now OptionTag::Applied<i32> == Option<i32>
85/// let x: <OptionTag as TypeApp>::Applied<i32> = Some(42);
86/// ```
87pub trait TypeApp {
88    /// The result of applying this type constructor to `X`.
89    type Applied<X>;
90}
91
92/// A **functor** for type constructors.
93///
94/// The `: TypeApp` bound is a kind signature — it says `Functor` is for
95/// type constructors (`* -> *`), not a behavioral dependency.
96///
97/// This is a functor in the category-theoretic sense: it maps objects
98/// (types) and morphisms (functions) while preserving identity and
99/// composition.
100///
101/// Laws (not enforced by type system):
102///
103/// - **Identity**: `fmap id = id`
104/// - **Composition**: `fmap g ∘ fmap f = fmap (g ∘ f)`
105///
106/// # Example
107///
108/// ```rust
109/// use algebra_core::fix::{TypeApp, Functor};
110///
111/// // A simple container
112/// struct Pair<X>(X, X);
113///
114/// struct PairTag;
115///
116/// impl TypeApp for PairTag {
117///     type Applied<X> = Pair<X>;
118/// }
119///
120/// impl Functor for PairTag {
121///     fn fmap<X, Y, G>(fx: Pair<X>, mut g: G) -> Pair<Y>
122///     where
123///         G: FnMut(X) -> Y,
124///     {
125///         Pair(g(fx.0), g(fx.1))
126///     }
127/// }
128/// ```
129pub trait Functor: TypeApp {
130    /// Map a function over the holes (type parameter positions).
131    fn fmap<X, Y, G>(fx: Self::Applied<X>, g: G) -> Self::Applied<Y>
132    where
133        G: FnMut(X) -> Y;
134}
135
136/// The **least fixed point** (μF) of a functor F.
137///
138/// `Fix<F>` satisfies the isomorphism:
139///
140/// ```text
141/// Fix<F> ≅ F(Fix<F>)
142/// ```
143///
144/// # Why `Fix<F>` satisfies `X ≅ F(X)` (for a Haskeller)
145///
146/// We freely mix real Haskell syntax with informal *type-equation
147/// expansions* to show what the types *mean*. Some lines below are
148/// explanatory and are not meant to compile literally.
149///
150/// Start with an ordinary recursive datatype:
151///
152/// ```haskell
153/// data List a = Nil | Cons a (List a)
154/// ```
155///
156/// Read this as a sum/product equation over values:
157///
158/// ```text
159/// List a ≅ 1 + (a × List a)
160/// ```
161///
162/// where:
163///
164/// - `1` is the singleton set (the `Nil` case),
165/// - `+` is disjoint sum (choice of constructor),
166/// - `×` is product (constructor arguments).
167///
168/// At this point we are no longer doing category theory — we are
169/// solving a *type equation*. A recursive datatype is precisely one
170/// whose values are defined in terms of smaller values of the same
171/// type. Writing the constructors this way makes that explicit: we
172/// are looking for a type `X` such that `X` appears on both sides of
173/// its own definition. That is exactly what a fixed point is.
174///
175/// Now factor out the recursive position by naming the shape that
176/// still has a hole.
177///
178/// In Haskell syntax:
179///
180/// ```haskell
181/// data ListF a x = Nil | Cons a x
182/// ```
183///
184/// Schematic (equational) form of the same declaration:
185///
186/// ```text
187/// ListF a X = 1 + (a × X)
188/// ```
189///
190/// This separates the *shape* of the structure from the recursion
191/// itself.
192///
193/// Substituting the original type back into the hole gives:
194///
195/// ```text
196/// List a ≅ ListF a (List a)
197/// ```
198///
199/// which is exactly the fixed-point equation:
200///
201/// ```text
202/// X ≅ F(X)
203/// ```
204///
205/// with `X = List a` and `F = ListF a`.
206///
207/// We write `≅` (isomorphism), not `=`, because recursive types are
208/// represented by wrappers, not by definitional equality. In Haskell
209/// this is explicit:
210///
211/// ```haskell
212/// newtype Fix f = Fix (f (Fix f))
213///
214/// unFix :: Fix f -> f (Fix f)
215/// ```
216///
217/// In Rust, direct self-recursion is not representable without
218/// indirection, so `Fix<F>` stores `F::Applied<Fix<F>>` inside a
219/// `Box`. This establishes the same isomorphism by construction
220/// rather than by definitional equality.
221///
222/// Unrolling makes the correspondence concrete by showing what "one
223/// layer" means.
224///
225/// Specialize `Fix` to lists:
226///
227/// ```haskell
228/// type List a = Fix (ListF a)
229/// ```
230///
231/// Now compute `unFix` at that specialization:
232///
233/// ```haskell
234/// unFix :: List a -> ListF a (List a)
235///       :: Fix (ListF a) -> ListF a (Fix (ListF a))
236///
237/// -- meta-level expansion of the constructors (not valid Haskell syntax)
238///       :: Fix (ListF a) -> (Nil | Cons a (Fix (ListF a)))
239/// ```
240///
241/// That final line is not Haskell code — it is the semantic expansion
242/// of the datatype. It shows that one step of unrolling a list yields
243/// exactly one constructor layer: either `Nil`, or `Cons a` paired
244/// with a recursive tail.
245///
246/// In Rust, the same unrolling exists with different names and
247/// explicit indirection:
248///
249/// ```text
250/// Fix<F>::out : Fix<F> -> F::Applied<Fix<F>>
251/// ```
252///
253/// For `F = ListTag<A>` where `F::Applied<X> = ListF<A, X>`:
254///
255/// ```text
256/// Fix<ListTag<A>>::out : Fix<ListTag<A>> -> ListF<A, Fix<ListTag<A>>>
257/// ```
258///
259/// The `Box` is purely representational — it makes the recursive type
260/// finite in memory — and does not change the mathematical meaning of
261/// the unrolling.
262///
263/// # Why "least" fixed point?
264///
265/// The equation `X = F(X)` can have multiple solutions, including
266/// infinite, non-well-founded ones. The least fixed point `μF` is the
267/// smallest solution closed under the constructors — the inductive
268/// (finite) values.
269///
270/// In domain-theoretic terms, `μF` is the join of an ascending chain:
271///
272/// ```text
273/// μF = ⊔{Fⁿ(⊥) | n ∈ ℕ}
274/// ```
275///
276/// For a concrete intuition, let:
277///
278/// ```text
279/// ExprF<X> = Lit(i32) | Add(X, X)
280/// ```
281///
282/// Then:
283///
284/// - `E₀ = ⊥` (empty)
285/// - `E₁ = Lit(n)`
286/// - `E₂ = Lit(n) | Add(Lit, Lit)`
287/// - `E₃ = Lit(n) | Add(E₂, E₂)`
288/// - ...
289/// - `μExprF = ⊔Eₙ` = all finite expression trees
290///
291/// Each stage adds one more layer of depth. The fixed point is the
292/// union of all finite stages — exactly the recursive datatype we
293/// intend to model.
294///
295/// # `Send` / `Sync`
296///
297/// `Fix<F>` is `Send` iff `F::Applied<Fix<F>>` is `Send`.
298/// `Fix<F>` is `Sync` iff `F::Applied<Fix<F>>` is `Sync`.
299///
300/// # Example
301///
302/// ```rust
303/// use algebra_core::fix::{TypeApp, Fix};
304///
305/// // Natural numbers: Nat = Zero | Succ(Nat)
306/// enum NatF<X> {
307///     Zero,
308///     Succ(X),
309/// }
310///
311/// struct NatTag;
312///
313/// impl TypeApp for NatTag {
314///     type Applied<X> = NatF<X>;
315/// }
316///
317/// type Nat = Fix<NatTag>;
318///
319/// // Smart constructors hide the Fix::new calls
320/// fn zero() -> Nat { Fix::new(NatF::Zero) }
321/// fn succ(n: Nat) -> Nat { Fix::new(NatF::Succ(n)) }
322///
323/// let two = succ(succ(zero()));
324/// ```
325#[repr(transparent)]
326pub struct Fix<F: TypeApp>(Box<F::Applied<Fix<F>>>);
327
328impl<F: TypeApp> Fix<F> {
329    /// Construct a `Fix` from one layer of the functor.
330    ///
331    /// This is the "in" morphism: `F(Fix F) → Fix F`
332    #[inline]
333    pub fn new(node: F::Applied<Fix<F>>) -> Self {
334        Fix(Box::new(node))
335    }
336
337    /// Unwrap one layer of the functor, consuming the `Fix`.
338    ///
339    /// This is the "out" morphism: `Fix F → F(Fix F)`
340    #[inline]
341    pub fn out(self) -> F::Applied<Fix<F>> {
342        *self.0
343    }
344
345    /// Borrow one layer of the functor.
346    #[inline]
347    pub fn as_out(&self) -> &F::Applied<Fix<F>> {
348        &self.0
349    }
350}
351
352impl<F> Clone for Fix<F>
353where
354    F: TypeApp,
355    F::Applied<Fix<F>>: Clone,
356{
357    fn clone(&self) -> Self {
358        Self(Box::new((*self.0).clone()))
359    }
360}
361
362impl<F> Debug for Fix<F>
363where
364    F: TypeApp,
365    F::Applied<Fix<F>>: Debug,
366{
367    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
368        f.debug_tuple("Fix").field(&*self.0).finish()
369    }
370}
371
372impl<F> PartialEq for Fix<F>
373where
374    F: TypeApp,
375    F::Applied<Fix<F>>: PartialEq,
376{
377    fn eq(&self, other: &Self) -> bool {
378        *self.0 == *other.0
379    }
380}
381
382impl<F> Eq for Fix<F>
383where
384    F: TypeApp,
385    F::Applied<Fix<F>>: Eq,
386{
387}
388
389/// A **catamorphism**: fold a recursive structure using an F-algebra.
390///
391/// Given an algebra `alg : F(A) → A`, this produces a function
392/// `Fix<F> → A` that recursively applies the algebra from the
393/// leaves up.
394///
395/// # Stack Safety
396///
397/// This implementation uses direct recursion. The risk of stack
398/// overflow is proportional to the **recursion depth**, not the total
399/// size of the structure.
400///
401/// - **Balanced trees**: Typically safe (depth ≈ log(n))
402/// - **Skewed structures** (e.g., long linked lists): May overflow
403///
404/// Stack overflow typically occurs around ~10k frames on common
405/// platforms, but this varies. For very deep structures, consider
406/// using the `stacker` crate or an iterative approach.
407///
408/// # Example
409///
410/// ```rust
411/// use algebra_core::fix::{TypeApp, Functor, Fix, fold};
412///
413/// // Expression tree: Expr = Lit(i32) | Add(Expr, Expr)
414/// enum ExprF<X> {
415///     Lit(i32),
416///     Add(X, X),
417/// }
418///
419/// struct ExprTag;
420///
421/// impl TypeApp for ExprTag {
422///     type Applied<X> = ExprF<X>;
423/// }
424///
425/// impl Functor for ExprTag {
426///     fn fmap<X, Y, G>(fx: ExprF<X>, mut g: G) -> ExprF<Y>
427///     where
428///         G: FnMut(X) -> Y,
429///     {
430///         match fx {
431///             ExprF::Lit(n) => ExprF::Lit(n),
432///             ExprF::Add(l, r) => ExprF::Add(g(l), g(r)),
433///         }
434///     }
435/// }
436///
437/// type Expr = Fix<ExprTag>;
438///
439/// fn lit(n: i32) -> Expr {
440///     Fix::new(ExprF::Lit(n))
441/// }
442///
443/// fn add(l: Expr, r: Expr) -> Expr {
444///     Fix::new(ExprF::Add(l, r))
445/// }
446///
447/// // Build: (1 + 2) + 3
448/// let expr = add(add(lit(1), lit(2)), lit(3));
449///
450/// // Evaluate via fold
451/// let result: i32 = fold(expr, |layer| match layer {
452///     ExprF::Lit(n) => n,
453///     ExprF::Add(l, r) => l + r,
454/// });
455/// assert_eq!(result, 6);
456/// ```
457pub fn fold<F, A>(t: Fix<F>, alg: impl FnMut(F::Applied<A>) -> A) -> A
458where
459    F: Functor,
460{
461    // Internal helper avoids moving the closure on each recursive call
462    fn go<F, A>(t: Fix<F>, alg: &mut impl FnMut(F::Applied<A>) -> A) -> A
463    where
464        F: Functor,
465    {
466        let node = t.out();
467        let mapped = F::fmap(node, |child| go::<F, A>(child, alg));
468        alg(mapped)
469    }
470
471    let mut alg = alg;
472    go::<F, A>(t, &mut alg)
473}
474
475#[cfg(test)]
476mod tests {
477    use super::*;
478
479    // Test fixtures: ListF as base functor
480
481    // Note: We don't derive Clone/Debug/PartialEq on ListF because
482    // the derived impls cause trait resolution overflow when used
483    // with Fix<ListTag<A>> (recursive type). Instead, we implement
484    // them manually or test without requiring these traits on Fix.
485    enum ListF<A, X> {
486        Nil,
487        Cons(A, X),
488    }
489
490    // Manual implementations for non-recursive X (used in functor law
491    // tests)
492    impl<A: Clone, X: Clone> Clone for ListF<A, X> {
493        fn clone(&self) -> Self {
494            match self {
495                ListF::Nil => ListF::Nil,
496                ListF::Cons(a, x) => ListF::Cons(a.clone(), x.clone()),
497            }
498        }
499    }
500
501    impl<A: PartialEq, X: PartialEq> PartialEq for ListF<A, X> {
502        fn eq(&self, other: &Self) -> bool {
503            match (self, other) {
504                (ListF::Nil, ListF::Nil) => true,
505                (ListF::Cons(a1, x1), ListF::Cons(a2, x2)) => a1 == a2 && x1 == x2,
506                _ => false,
507            }
508        }
509    }
510
511    impl<A: Eq, X: Eq> Eq for ListF<A, X> {}
512
513    impl<A: Debug, X: Debug> Debug for ListF<A, X> {
514        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
515            match self {
516                ListF::Nil => write!(f, "Nil"),
517                ListF::Cons(a, x) => f.debug_tuple("Cons").field(a).field(x).finish(),
518            }
519        }
520    }
521
522    struct ListTag<A>(std::marker::PhantomData<A>);
523
524    impl<A> TypeApp for ListTag<A> {
525        type Applied<X> = ListF<A, X>;
526    }
527
528    impl<A: Clone> Functor for ListTag<A> {
529        fn fmap<X, Y, G>(fx: ListF<A, X>, mut g: G) -> ListF<A, Y>
530        where
531            G: FnMut(X) -> Y,
532        {
533            match fx {
534                ListF::Nil => ListF::Nil,
535                ListF::Cons(a, x) => ListF::Cons(a.clone(), g(x)),
536            }
537        }
538    }
539
540    type List<A> = Fix<ListTag<A>>;
541
542    fn nil<A: Clone>() -> List<A> {
543        Fix::new(ListF::Nil)
544    }
545
546    fn cons<A: Clone>(head: A, tail: List<A>) -> List<A> {
547        Fix::new(ListF::Cons(head, tail))
548    }
549
550    fn from_vec<A: Clone>(v: Vec<A>) -> List<A> {
551        v.into_iter().rev().fold(nil(), |acc, x| cons(x, acc))
552    }
553
554    // Helper to convert List back to Vec for comparison
555    fn to_vec(list: List<i32>) -> Vec<i32> {
556        fold(list, |layer: ListF<i32, Vec<i32>>| match layer {
557            ListF::Nil => Vec::new(),
558            ListF::Cons(a, mut acc) => {
559                acc.insert(0, a);
560                acc
561            }
562        })
563    }
564
565    // Fix tests
566
567    #[test]
568    fn fix_new_and_out_are_inverses() {
569        // Test with a non-recursive inner type to avoid overflow
570        let layer: ListF<i32, String> = ListF::Cons(42, "tail".to_string());
571        let fixed: Fix<ListTag<i32>> = Fix(Box::new(ListF::Cons(42, nil())));
572
573        // Verify we can unwrap and get back the expected structure
574        match fixed.out() {
575            ListF::Cons(head, _tail) => assert_eq!(head, 42),
576            ListF::Nil => panic!("expected Cons"),
577        }
578
579        // Also test with Nil
580        let nil_fixed: Fix<ListTag<i32>> = Fix::new(ListF::Nil);
581        match nil_fixed.out() {
582            ListF::Nil => {}
583            ListF::Cons(_, _) => panic!("expected Nil"),
584        }
585
586        // Verify layer clone works for non-recursive case
587        let cloned = layer.clone();
588        assert_eq!(cloned, layer);
589    }
590
591    #[test]
592    fn fix_as_out_borrows_layer() {
593        let list = cons(1, cons(2, nil()));
594        match list.as_out() {
595            ListF::Cons(head, _) => assert_eq!(*head, 1),
596            ListF::Nil => panic!("expected Cons"),
597        }
598    }
599
600    #[test]
601    fn fix_operations_preserve_structure() {
602        // Build a list and verify via fold that structure is
603        // preserved
604        let list = cons(1, cons(2, cons(3, nil())));
605        let vec = to_vec(list);
606        assert_eq!(vec, vec![1, 2, 3]);
607    }
608
609    // fold tests
610
611    #[test]
612    fn fold_computes_length() {
613        let list = from_vec(vec![1, 2, 3, 4, 5]);
614        let length: usize = fold(list, |layer: ListF<i32, usize>| match layer {
615            ListF::Nil => 0,
616            ListF::Cons(_, n) => n + 1,
617        });
618        assert_eq!(length, 5);
619    }
620
621    #[test]
622    fn fold_computes_sum() {
623        let list = from_vec(vec![1, 2, 3, 4, 5]);
624        let sum: i32 = fold(list, |layer: ListF<i32, i32>| match layer {
625            ListF::Nil => 0,
626            ListF::Cons(a, acc) => a + acc,
627        });
628        assert_eq!(sum, 15);
629    }
630
631    #[test]
632    fn fold_converts_to_vec() {
633        let list = from_vec(vec![1, 2, 3]);
634        let vec = to_vec(list);
635        assert_eq!(vec, vec![1, 2, 3]);
636    }
637
638    #[test]
639    fn fold_empty_list() {
640        let list: List<i32> = nil();
641        let length: usize = fold(list, |layer: ListF<i32, usize>| match layer {
642            ListF::Nil => 0,
643            ListF::Cons(_, n) => n + 1,
644        });
645        assert_eq!(length, 0);
646    }
647
648    // Functor law tests (using non-recursive X to enable
649    // Clone/PartialEq)
650
651    #[test]
652    fn functor_identity_law() {
653        // fmap(fx, |x| x) == fx
654        let layer: ListF<i32, String> = ListF::Cons(42, "tail".to_string());
655        let mapped = ListTag::<i32>::fmap(layer.clone(), |x| x);
656        assert_eq!(mapped, layer);
657    }
658
659    #[test]
660    fn functor_identity_law_nil() {
661        let layer: ListF<i32, String> = ListF::Nil;
662        let mapped = ListTag::<i32>::fmap(layer.clone(), |x| x);
663        assert_eq!(mapped, layer);
664    }
665
666    #[test]
667    fn functor_composition_law() {
668        // fmap(fmap(fx, f), g) == fmap(fx, |x| g(f(x)))
669        let layer: ListF<i32, i32> = ListF::Cons(1, 10);
670
671        let f = |x: i32| x * 2;
672        let g = |x: i32| x + 1;
673
674        // fmap twice
675        let left = ListTag::<i32>::fmap(ListTag::<i32>::fmap(layer.clone(), f), g);
676
677        // fmap once with composition
678        let right = ListTag::<i32>::fmap(layer, |x| g(f(x)));
679
680        assert_eq!(left, right);
681    }
682
683    #[test]
684    fn functor_composition_law_nil() {
685        let layer: ListF<i32, i32> = ListF::Nil;
686
687        let f = |x: i32| x * 2;
688        let g = |x: i32| x + 1;
689
690        let left = ListTag::<i32>::fmap(ListTag::<i32>::fmap(layer.clone(), f), g);
691        let right = ListTag::<i32>::fmap(layer, |x| g(f(x)));
692
693        assert_eq!(left, right);
694    }
695
696    // Expression tree example (from docs)
697
698    enum ExprF<X> {
699        Lit(i32),
700        Add(X, X),
701    }
702
703    struct ExprTag;
704
705    impl TypeApp for ExprTag {
706        type Applied<X> = ExprF<X>;
707    }
708
709    impl Functor for ExprTag {
710        fn fmap<X, Y, G>(fx: ExprF<X>, mut g: G) -> ExprF<Y>
711        where
712            G: FnMut(X) -> Y,
713        {
714            match fx {
715                ExprF::Lit(n) => ExprF::Lit(n),
716                ExprF::Add(l, r) => ExprF::Add(g(l), g(r)),
717            }
718        }
719    }
720
721    type Expr = Fix<ExprTag>;
722
723    fn lit(n: i32) -> Expr {
724        Fix::new(ExprF::Lit(n))
725    }
726
727    fn add(l: Expr, r: Expr) -> Expr {
728        Fix::new(ExprF::Add(l, r))
729    }
730
731    #[test]
732    fn expr_eval_via_fold() {
733        // (1 + 2) + 3 = 6
734        let expr = add(add(lit(1), lit(2)), lit(3));
735        let result: i32 = fold(expr, |layer: ExprF<i32>| match layer {
736            ExprF::Lit(n) => n,
737            ExprF::Add(l, r) => l + r,
738        });
739        assert_eq!(result, 6);
740    }
741
742    #[test]
743    fn expr_count_nodes_via_fold() {
744        // (1 + 2) + 3 has 5 nodes: 3 Lit, 2 Add
745        let expr = add(add(lit(1), lit(2)), lit(3));
746        let count: usize = fold(expr, |layer: ExprF<usize>| match layer {
747            ExprF::Lit(_) => 1,
748            ExprF::Add(l, r) => 1 + l + r,
749        });
750        assert_eq!(count, 5);
751    }
752
753    #[test]
754    fn expr_to_string_via_fold() {
755        let expr = add(add(lit(1), lit(2)), lit(3));
756        let s: String = fold(expr, |layer: ExprF<String>| match layer {
757            ExprF::Lit(n) => n.to_string(),
758            ExprF::Add(l, r) => format!("({} + {})", l, r),
759        });
760        assert_eq!(s, "((1 + 2) + 3)");
761    }
762}