Zygomorphism
Tutorial Video
Text description (accessibility)
This video demonstrates the "Zygomorphism" functional Rust example. Difficulty level: Expert. Key concepts covered: Functional Programming. A zygomorphism (zygo) is a mutually recursive fold where two computations run simultaneously, one depending on the other. Key difference from OCaml: 1. **HKT requirement**: These morphisms ideally require higher
Tutorial
The Problem
A zygomorphism (zygo) is a mutually recursive fold where two computations run simultaneously, one depending on the other. The classic example: compute both the value and its depth simultaneously in a single tree traversal. Zygomorphisms generalize paramorphisms and are more efficient than running two separate traversals.
🎯 Learning Outcomes
Code Example
#![allow(clippy::all)]
//! # Zygomorphism
//! Two folds running in parallel.
pub fn zygo<A, B, C>(
xs: &[A],
nil_b: B,
cons_b: impl Fn(&A, &B) -> B,
nil_c: C,
cons_c: impl Fn(&A, &B, C) -> C,
) -> C
where
B: Clone,
{
let mut bs: Vec<B> = vec![nil_b.clone()];
for x in xs.iter().rev() {
bs.push(cons_b(x, bs.last().unwrap()));
}
bs.reverse();
let mut c = nil_c;
for (i, x) in xs.iter().enumerate().rev() {
c = cons_c(x, &bs[i + 1], c);
}
c
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_zygo() {
let sum = zygo(&[1, 2, 3], 0, |x, b| x + b, 0, |_, _, c| c + 1);
assert_eq!(sum, 3);
}
}Key Differences
recursion-schemes are widely used; in Rust and OCaml, direct recursion is more common in practice.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)]
//! # Zygomorphism
//! Two folds running in parallel.
pub fn zygo<A, B, C>(
xs: &[A],
nil_b: B,
cons_b: impl Fn(&A, &B) -> B,
nil_c: C,
cons_c: impl Fn(&A, &B, C) -> C,
) -> C
where
B: Clone,
{
let mut bs: Vec<B> = vec![nil_b.clone()];
for x in xs.iter().rev() {
bs.push(cons_b(x, bs.last().unwrap()));
}
bs.reverse();
let mut c = nil_c;
for (i, x) in xs.iter().enumerate().rev() {
c = cons_c(x, &bs[i + 1], c);
}
c
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_zygo() {
let sum = zygo(&[1, 2, 3], 0, |x, b| x + b, 0, |_, _, c| c + 1);
assert_eq!(sum, 3);
}
}#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_zygo() {
let sum = zygo(&[1, 2, 3], 0, |x, b| x + b, 0, |_, _, c| c + 1);
assert_eq!(sum, 3);
}
}
Deep Comparison
Zygomorphism
Two folds with one depending on the other