ExamplesBy LevelBy TopicLearning Paths
614 Expert

Histomorphism

Functional Programming

Tutorial Video

Text description (accessibility)

This video demonstrates the "Histomorphism" functional Rust example. Difficulty level: Expert. Key concepts covered: Functional Programming. A histomorphism (histo) is a catamorphism where the fold function has access to the entire history of previous fold results, not just the immediate result. Key difference from OCaml: 1. **HKT requirement**: These morphisms ideally require higher

Tutorial

The Problem

A histomorphism (histo) is a catamorphism where the fold function has access to the entire history of previous fold results, not just the immediate result. The cofree comonad of the base functor carries the history. This enables computing Fibonacci in O(n) with a single pass, longest increasing subsequence, and dynamic programming patterns that need previous computed values.

🎯 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: histo, fold with history, cofree comonad, dynamic programming
  • • Where this pattern appears in production systems and when simpler alternatives suffice
  • Code Example

    #![allow(clippy::all)]
    //! # Histomorphism
    //! Fold with access to all previous results.
    
    pub fn histo<A, B: Clone>(xs: &[A], nil: B, cons: impl Fn(&A, &[B]) -> B) -> B {
        let mut history = vec![nil];
        for x in xs.iter().rev() {
            let next = cons(x, &history);
            history.push(next);
        }
        history.pop().unwrap()
    }
    
    pub fn fib_histo(n: usize) -> u64 {
        if n == 0 {
            return 0;
        }
        if n == 1 {
            return 1;
        }
        let xs: Vec<()> = vec![(); n];
        histo(&xs, 0u64, |_, hist| {
            if hist.len() < 2 {
                1
            } else {
                hist[hist.len() - 1] + hist[hist.len() - 2]
            }
        })
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
        #[test]
        fn test_fib() {
            assert_eq!(fib_histo(10), 55);
        }
    }

    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)]
    //! # Histomorphism
    //! Fold with access to all previous results.
    
    pub fn histo<A, B: Clone>(xs: &[A], nil: B, cons: impl Fn(&A, &[B]) -> B) -> B {
        let mut history = vec![nil];
        for x in xs.iter().rev() {
            let next = cons(x, &history);
            history.push(next);
        }
        history.pop().unwrap()
    }
    
    pub fn fib_histo(n: usize) -> u64 {
        if n == 0 {
            return 0;
        }
        if n == 1 {
            return 1;
        }
        let xs: Vec<()> = vec![(); n];
        histo(&xs, 0u64, |_, hist| {
            if hist.len() < 2 {
                1
            } else {
                hist[hist.len() - 1] + hist[hist.len() - 2]
            }
        })
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
        #[test]
        fn test_fib() {
            assert_eq!(fib_histo(10), 55);
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
        #[test]
        fn test_fib() {
            assert_eq!(fib_histo(10), 55);
        }
    }

    Deep Comparison

    Histomorphism

    Fold with access to all prior results

    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