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 encodingRepresenting data as functions in the pure lambda calculus: the numeral n applies its argument n times, true and false choose between two arguments, and a pair waits for a selector. Addition, multiplication and exponentiation are one line each; the predecessor needs a trick and takes time linear in n.
- 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.
- FunctorA type constructor f with a map operation, fmap : (a -> b) -> f a -> f b, that preserves identity and composition. Lists, options, trees and functions out of a fixed type are functors. In OCaml the word also means something else: a module parametrised by another module, such as Map.Make.
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).