Hylomorphism
A function that unfolds a seed into a recursive structure and folds that structure into a result, written so the structure is never built: hylo f g = f . fmap (hylo f g) . g. The call tree of the recursion is the intermediate structure.
For a functor with least fixed point and with inverse , a catamorphism is determined by an algebra and an anamorphism by a coalgebra :
Composing them builds a value only to take it apart. Since and preserves composition,
so the composite satisfies the equation of the hylomorphism, which does both in one recursion and allocates no at all:
Hylomorphisms over the list functor and over the leaf-tree functor.
(* The base functor of lists: one layer, with the recursive position abstracted. *)type ('a, 'r) listf = Nil | Cons of 'a * 'rlet map_list f = function Nil -> Nil | Cons (a, r) -> Cons (a, f r)(* hylo alg coalg = alg . fmap (hylo alg coalg) . coalg *)let rec hylo_list alg coalg x = alg (map_list (hylo_list alg coalg) (coalg x))(* factorial: unfold n into n, n-1, ..., 1 and fold with multiplication,without ever building the list. *)let fact =hylo_list(function Nil -> 1 | Cons (a, r) -> a * r)(fun n -> if n = 0 then Nil else Cons (n, n - 1))(* The base functor of binary trees with values at the leaves. *)type ('a, 'r) treef = Empty | Single of 'a | Split of 'r * 'rlet map_tree f = function Empty -> Empty | Single a -> Single a | Split (l, r) -> Split (f l, f r)let rec hylo_tree alg coalg x = alg (map_tree (hylo_tree alg coalg) (coalg x))let rec merge xs ys =match (xs, ys) with| [], l | l, [] -> l| x :: xs', y :: ys' -> if x <= y then x :: merge xs' ys else y :: merge xs ys'(* Merge sort: the call tree of the recursion is the intermediate tree. *)let msort =hylo_tree(function Empty -> [] | Single a -> [ a ] | Split (l, r) -> merge l r)(function| [] -> Empty| [ a ] -> Single a| xs ->let n = List.length xs / 2 inSplit (List.filteri (fun i _ -> i < n) xs, List.filteri (fun i _ -> i >= n) xs))
Running it.
fact 10 -> 3628800msort [5;2;8;3;7;1;4] -> [1; 2; 3; 4; 5; 7; 8]
Factorial unfolds into and multiplies, and no list exists. Merge sort splits a list in half as its coalgebra and merges as its algebra, and the tree is only the shape of the recursive calls. Quicksort is the same with a split around a pivot and concatenation as the algebra.
Meijer, Fokkinga and Paterson named these schemes and gave the laws relating them.[1] The fusion law, whenever and is strict, is the basis of deforestation in compilers.
see also
- Church encodingChurch encoding represents data in the pure lambda calculus, which has only functions, by functions: a natural number n is the function that applies its argument n times, a boolean is a function choosing between two alternatives, and a pair is a function waiting for a selector. In general a value is represented by its own fold. Arithmetic, logic and data structures all become lambda terms, which is how the lambda calculus was shown to express every computable function; in typed form the encoding needs polymorphism, as in System F.
- RepminReplace every leaf of a tree by the tree's minimum, in one traversal. The traversal returns the minimum and the rebuilt tree together, and the rebuilt tree's leaves refer to the minimum the same traversal is still computing. It works because the reference is lazy.
- Yoneda lemmaFor a functor F and an object a, natural transformations from Hom(a, -) to F correspond one to one with elements of F a. In Haskell, forall b. (a -> b) -> f b is isomorphic to f a, and the left-hand form turns a chain of fmaps into one composed function and a single fmap.
- CatamorphismA catamorphism is the generalization of fold from lists to any recursive data type: it replaces each constructor of a value with a function and combines the results bottom up. Formally it is the unique homomorphism from the initial algebra of a functor to any other algebra of that functor. Writing a recursive data type as the fixed point of a base functor lets a single cata function fold every such type, with the recursion written once and each computation reduced to an algebra that handles one layer.
- AnamorphismAn anamorphism is the generalization of unfold: it builds a value of a recursive data type from a seed, using a function (a coalgebra) that produces one layer of the structure together with new seeds for its recursive positions. It is the categorical dual of the catamorphism, the unique homomorphism from any coalgebra into the final coalgebra of a functor. Anamorphisms can produce infinite structures such as streams, and they are the way to write corecursive programs that are guaranteed to be productive.
- FunctorIn functional programming, a functor is a type constructor f with an operation fmap : (a -> b) -> f a -> f b that applies a function inside the structure without changing its shape, preserving identity and composition. Lists, options, trees and functions out of a fixed type are functors. The notion comes from category theory. In OCaml the word also names parametrised modules such as Map.Make.
- ParamorphismA paramorphism is a recursion scheme that generalizes a fold: at each step it receives not only the result of the recursive calls on the substructures but also the substructures themselves. On natural numbers it is primitive recursion. Functions such as factorial, the list of suffixes, or insertion into a sorted list, which need the original tail as well as a result computed from it, are paramorphisms.
further reading
- [1]E. Meijer, M. Fokkinga, R. Paterson, “Functional programming with bananas, lenses, envelopes and barbed wire”, FPCA (1991).
- [2]P. Wadler, “Deforestation: transforming programs to eliminate trees”, Theoretical Computer Science 73 (1990).
last updated