ExamplesBy LevelBy TopicLearning Paths
608 Expert

Catamorphism

Functional Programming

Tutorial Video

Text description (accessibility)

This video demonstrates the "Catamorphism" functional Rust example. Difficulty level: Expert. Key concepts covered: Functional Programming. A catamorphism (fold/cata) is a generalized fold over a recursive data structure. Key difference from OCaml: 1. **HKT requirement**: These morphisms ideally require higher

Tutorial

The Problem

A catamorphism (fold/cata) is a generalized fold over a recursive data structure. Any fold can be expressed as a catamorphism. The F-algebra (algebra of the base functor) specifies how to collapse one level; the catamorphism applies it recursively bottom-up. This generalizes List.fold_right, tree fold, and any recursive descent. The term comes from Meijer et al. 1991 Bananas, Lenses, Envelopes and Barbed Wire.

🎯 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: cata, F-algebra, fold over Fix, bottom-up recursion
  • • Where this pattern appears in production systems and when simpler alternatives suffice
  • Code Example

    #![allow(clippy::all)]
    //! # Catamorphism (Fold)
    //! Generalized fold over recursive structures.
    
    pub fn cata_list<A, B>(list: &[A], nil: B, cons: impl Fn(&A, B) -> B) -> B {
        list.iter().rev().fold(nil, |acc, x| cons(x, acc))
    }
    
    pub fn sum(xs: &[i32]) -> i32 {
        cata_list(xs, 0, |x, acc| x + acc)
    }
    pub fn product(xs: &[i32]) -> i32 {
        cata_list(xs, 1, |x, acc| x * acc)
    }
    pub fn length<A>(xs: &[A]) -> usize {
        cata_list(xs, 0, |_, acc| acc + 1)
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
        #[test]
        fn test_sum() {
            assert_eq!(sum(&[1, 2, 3]), 6);
        }
        #[test]
        fn test_product() {
            assert_eq!(product(&[1, 2, 3, 4]), 24);
        }
        #[test]
        fn test_length() {
            assert_eq!(length(&[1, 2, 3]), 3);
        }
    }

    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)]
    //! # Catamorphism (Fold)
    //! Generalized fold over recursive structures.
    
    pub fn cata_list<A, B>(list: &[A], nil: B, cons: impl Fn(&A, B) -> B) -> B {
        list.iter().rev().fold(nil, |acc, x| cons(x, acc))
    }
    
    pub fn sum(xs: &[i32]) -> i32 {
        cata_list(xs, 0, |x, acc| x + acc)
    }
    pub fn product(xs: &[i32]) -> i32 {
        cata_list(xs, 1, |x, acc| x * acc)
    }
    pub fn length<A>(xs: &[A]) -> usize {
        cata_list(xs, 0, |_, acc| acc + 1)
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
        #[test]
        fn test_sum() {
            assert_eq!(sum(&[1, 2, 3]), 6);
        }
        #[test]
        fn test_product() {
            assert_eq!(product(&[1, 2, 3, 4]), 24);
        }
        #[test]
        fn test_length() {
            assert_eq!(length(&[1, 2, 3]), 3);
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
        #[test]
        fn test_sum() {
            assert_eq!(sum(&[1, 2, 3]), 6);
        }
        #[test]
        fn test_product() {
            assert_eq!(product(&[1, 2, 3, 4]), 24);
        }
        #[test]
        fn test_length() {
            assert_eq!(length(&[1, 2, 3]), 3);
        }
    }

    Deep Comparison

    Catamorphism

    Fold: tear down structure bottom-up

    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