ExamplesBy LevelBy TopicLearning Paths
609 Expert

Anamorphism

Functional Programming

Tutorial Video

Text description (accessibility)

This video demonstrates the "Anamorphism" functional Rust example. Difficulty level: Expert. Key concepts covered: Functional Programming. An anamorphism (unfold/ana) is the categorical dual of a catamorphism — it builds a recursive structure top-down from a seed. Key difference from OCaml: 1. **HKT requirement**: These morphisms ideally require higher

Tutorial

The Problem

An anamorphism (unfold/ana) is the categorical dual of a catamorphism — it builds a recursive structure top-down from a seed. While catamorphisms consume structures, anamorphisms produce them. The F-coalgebra specifies how to expand one level from a seed value. Examples: generating a list from a range, unfolding a tree from a depth-first generator, producing a stream from a state.

🎯 Learning Outcomes

  • • The categorical definition and the mathematical laws that must hold
  • • How to implement this pattern in Rust despite the lack of higher-kinded types
  • • The relationship to more familiar functional idioms (fold, unfold, map)
  • • Key concepts: ana, F-coalgebra, unfold, top-down structure generation
  • • Where this pattern appears in production systems and when simpler alternatives suffice
  • Code Example

    #![allow(clippy::all)]
    //! # Anamorphism (Unfold)
    //! Generalized unfold to build recursive structures.
    
    pub fn ana<S, A>(seed: S, next: impl Fn(S) -> Option<(A, S)>) -> Vec<A> {
        let mut result = Vec::new();
        let mut s = seed;
        while let Some((a, s2)) = next(s) {
            result.push(a);
            s = s2;
        }
        result
    }
    
    pub fn range(start: i32, end: i32) -> Vec<i32> {
        ana(start, |n| if n < end { Some((n, n + 1)) } else { None })
    }
    
    pub fn iterate<A: Clone>(init: A, f: impl Fn(&A) -> A, n: usize) -> Vec<A> {
        ana((init, n), |(a, count)| {
            if count > 0 {
                Some((a.clone(), (f(&a), count - 1)))
            } else {
                None
            }
        })
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
        #[test]
        fn test_range() {
            assert_eq!(range(0, 5), vec![0, 1, 2, 3, 4]);
        }
        #[test]
        fn test_iterate() {
            assert_eq!(iterate(1, |x| x * 2, 4), vec![1, 2, 4, 8]);
        }
    }

    Key Differences

  • HKT requirement: These morphisms ideally require higher-kinded types for full generality; Rust uses GATs or associated types as approximations.
  • Performance: Rust's implementations are more verbose but compile to efficient machine code; OCaml's implementations are more concise with similar runtime performance.
  • Practical adoption: In Haskell, recursive schemes from recursion-schemes are widely used; in Rust and OCaml, direct recursion is more common in practice.
  • Theoretical value: Understanding these patterns deepens intuition for all recursive programming, even when direct recursion is used in production code.
  • OCaml Approach

    OCaml's pattern matching and recursive types make morphism implementations natural. The Fix type and F-algebra/coalgebra patterns translate directly, though without Haskell's typeclass machinery:

    (* OCaml recursive schemes use:
       - Recursive variant types for F-algebras
       - Higher-order functions for the morphism
       - GADTs for type-safe fixed points in advanced cases *)
    

    Full Source

    #![allow(clippy::all)]
    //! # Anamorphism (Unfold)
    //! Generalized unfold to build recursive structures.
    
    pub fn ana<S, A>(seed: S, next: impl Fn(S) -> Option<(A, S)>) -> Vec<A> {
        let mut result = Vec::new();
        let mut s = seed;
        while let Some((a, s2)) = next(s) {
            result.push(a);
            s = s2;
        }
        result
    }
    
    pub fn range(start: i32, end: i32) -> Vec<i32> {
        ana(start, |n| if n < end { Some((n, n + 1)) } else { None })
    }
    
    pub fn iterate<A: Clone>(init: A, f: impl Fn(&A) -> A, n: usize) -> Vec<A> {
        ana((init, n), |(a, count)| {
            if count > 0 {
                Some((a.clone(), (f(&a), count - 1)))
            } else {
                None
            }
        })
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
        #[test]
        fn test_range() {
            assert_eq!(range(0, 5), vec![0, 1, 2, 3, 4]);
        }
        #[test]
        fn test_iterate() {
            assert_eq!(iterate(1, |x| x * 2, 4), vec![1, 2, 4, 8]);
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
        #[test]
        fn test_range() {
            assert_eq!(range(0, 5), vec![0, 1, 2, 3, 4]);
        }
        #[test]
        fn test_iterate() {
            assert_eq!(iterate(1, |x| x * 2, 4), vec![1, 2, 4, 8]);
        }
    }

    Deep Comparison

    Anamorphism

    Unfold: build structure from seed

    Exercises

  • Laws verification: Write tests that verify the categorical laws for this morphism on a specific data type.
  • New data type: Apply the morphism to a different recursive data type (e.g., apply catamorphism to a rose tree instead of a binary tree).
  • Comparison: Implement the same computation using direct recursion and the morphism — measure whether the morphism version composes more cleanly.
  • Open Source Repos