Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Theory notes

hylic implements patterns from the theory of recursion schemes, adapted for Rust’s type system. This page maps hylic’s types to their formal names.

Catamorphism

A catamorphism is a bottom-up fold over a recursive structure. The algebra is F R → R — given one layer of structure with children already folded to R, produce R. The carrier type R is the result at every subtree.

hylic factors this algebra into three steps through an intermediate type H:

F R → R  =  init(&N) → H, accumulate(&mut H, &R) per child, finalize(&H) → R

H is mutable working state internal to each node. R is the immutable result that flows between nodes. The bracket (init opens H, finalize closes to R) makes the invariant boundary explicit. See The N-H-R algebra factorization for the comparison with Milewski’s monoidal decomposition and the equivalence under associative .

Hylomorphism

When the tree structure is not materialized but discovered on demand (via a Treeish backed by lazy child discovery), the unfold (anamorphism) and fold (catamorphism) fuse — the tree exists only as a call stack, never as a data structure. This is a hylomorphism.

In hylic, every Exec::run() call is a hylomorphism: the executor receives a coalgebra (Treeish<N>, which produces children on demand) and an algebra (FoldOps<N, H, R>, which consumes them), and fuses both in a single recursive pass. (N, Treeish<N>) is hylic’s runtime equivalent of the type-level Fix (f a) — the pair describes a root and a way to get children, recursively, without materializing the tree.

The Funnel executor parallelizes the hylomorphism using CPS and defunctionalized tasks.

Anamorphism (seed-based discovery)

An anamorphism builds recursive structure from a seed. SeedPipeline encapsulates this: given a seed edge function (Edgy<N, Seed>) and a grow function (Fn(&Seed) → N), it constructs the treeish by composing seeds_from_node.map(grow) and handles the entry transition. Internally, SeedLift implements Lift to express the SeedNode<N> indirection as a fold transformation.

Histomorphism (fold with history)

The Explainer records the full computation trace at every node — initial heap, each child result folded in, and the final result. This corresponds to a histomorphism: a catamorphism where each node has access to the full computation history of its subtree.

The Explainer’s output (ExplainerResult) is analogous to the cofree comonad annotation. It is expressed as a Lift — a fold transformation that changes the carrier types (H → ExplainerHeap, R → ExplainerResult). The original R is accessible via ExplainerResult::orig_result.

Algebra morphism (Lift)

Lift<N, N2> maps one fold algebra into another. It transforms the carrier types through two GATs (MapH<H, R>, MapR<H, R>) and can change the node type (N → N2) by extending the tree structure with new constructors.

The SeedLift extends the tree with an entry-root constructor (SeedNode<N>: EntryRoot, Node) — the EntryRoot row’s children are the per-seed grown nodes. The Explainer enriches the heap with trace data without changing the node type. Both are algebra morphisms: they transform the F R → R algebra into a different F' R' → R' algebra over a richer domain.

lift::run_lifted applies the three trait methods (lift_treeish, lift_fold, lift_root), runs the lifted computation, and returns MapR<H, R>.

Externalized tree structure

Classical recursion schemes encode tree structure via fixed points of functors (Fix F). The functor F defines one layer of shape (leaf, binary node, n-ary node), and Fix F is the recursive nesting.

hylic externalizes this as a runtime function: Treeish<N> is Fn(&N, &mut dyn FnMut(&N)). The node type N carries identity, not structure — the same N can be traversed by different treeish functions, and the same fold works with any tree shape. This trades compile-time structural guarantees for the orthogonal decomposition of fold, graph, and executor.

The pair (N, Treeish<N>) corresponds to a coalgebra N → F N — the treeish IS the coalgebra, producing one layer of children on demand. Combined with the fold algebra, the executor performs a fused hylomorphism.

Operations traits and domain abstraction

FoldOps<N, H, R> and TreeOps<N> abstract the fold and graph operations from their storage. The standard types (Fold, Treeish) store closures behind Arc (the Shared domain). Alternative implementations can use Rc (Local), Box (Owned), or concrete structs (zero-boxing). The executor’s recursion engine takes &impl FoldOps + &impl TreeOps — fully generic over the storage, monomorphized to zero overhead for concrete types.

The Domain trait with GATs maps the marker type (Shared, Local, Owned) to concrete fold types. Graph types are domain-independent — always Arc-based, always Send + Sync. The domain controls only how fold closures are stored.

Further reading

  • Meijer, Fokkinga, Paterson. Functional Programming with Bananas, Lenses, Envelopes and Barbed Wire. (1991) — the original recursion schemes paper.
  • Milewski. Monoidal Catamorphisms (2020) — a different algebra factorization. See comparison.
  • Gonzalez. foldl — the left-fold-with-extraction pattern.
  • Kmett. recursion-schemes — Haskell reference implementation.
  • Malick. recursion.wtf — practical recursion schemes in Rust.