Recursion / Co-recursion

Matthias van der Hallen

The main idea is that co-recursion is the dual of recursion;


but in what sense?



  • Terminology 'focuses' on the input (domain): an inductively defined datatype
  • Inductively defined datatypes corresponds to a least fixpoint
    • E.g. finite lists, finite trees, ... (data)
  • Corresponds to a 'fold'
  • Terminology 'focuses' on the output (codomain): a co-inductively defined datatype
  • co-inductive datatype corresponds to a greatest fixpoint
    • ​E.g. infinite lists (streams), infinite trees, ... (codata)
  • Corresponds to an 'unfold'

Example: Fibonacci

fibonacci :: Int -> Int
fibonacci 0 = 0
fibonacci 1 = 1
fibonacci n = fibonacci (n-1) + fibonacci (n-2)

A recursive definition of fibonacci breaks down its argument and returns a number

The idea of this being a 'fold' is probably more relatable by looking at natural numbers as a Peano numbers

(e.g. \(S(S(S(S Zero)))\))

Example: Fibonacci

co-fibonacci :: Int -> Int -> [Int]
co-fibonacci a b = a `cons` co-fibonacci b (a+b)

A co-recursive definition of fibonacci builds an infinite list of fibonacci numbers by specifying part of the output

This function 'unfolds' a pair of integers into an (infinite) list of integers.

Note that in a language with support for codata, e.g. with lazy evaluation, infinite lists are not a problem.

(Literature suggests a link here with Prolog & tail recursion module cons - think of an accumulator - and possibly bottom-up / top-down through that)

What if a function both breaks its arguments down and builds its result up?

Example: sorting functions with type
sort :: [a] -> [a]

  • Can be seen as a fold, an unfold or a fold of an unfold (or vice versa)
  • The paper 'A duality of sorts' views (pairs of) sorting algorithms as folds/unfolds of unfolds/folds.
    • They make a nice comparison between insertion-sort and bubblesort

More on datatypes & (un)-folding:

The List type

NatListF f = Nil | Cons Nat f

Using open recursion instead of explicit recursion, and specializing to natural numbers, we can write

The least fixpoint is written as \( \mu \mathit{NatListF} \), while the greatest fixpoint is written as \( \nu \mathit{NatListF} \).

List a = Nil | Cons a List

We recognize this as the definition of a list:

NatListF f = Nil | Cons Nat f

The least fixpoint is written as \( \mu \mathit{NatListF} \), while the greatest fixpoint is written as \( \nu \mathit{NatListF} \).

Folding corresponds to using the isomorphism between \(\mu F\) and \(F (\mu F)\), as well as the algebra \(F a \rightarrow a\)

Unfolding corresponds to using the co-algebra \(a \rightarrow F a\), as well as the isomorphism between \(F (\nu F)\) and \(\nu F\)

More on datatypes & (un)-folding:

The List type

Co-recursion: A (helpful?) visualization

A slightly more complicated unfolding (futumorphism, *) can take into account the structure already unfolded.

(*): Every futumorphism can be rewritten into a (generalized) unfolding

Example: How to 'grow' a plant according to a complex set of rules based on one 'seed' value

Looking at the seed, it (randomly) branches, blooms or grows straight, leaving a new seed at the end of the structure

Note how at every 'iteration' (step), it looks as if we recurse through our result and expand where necessary; we co-recurse

Does it correspond to the Owned Shares problem?

  • By choosing a smart result datatype, we can probably provide a co-recursive view on the problem, but...
  • it doesn't look like the process we want the solver to do, nor our semantical model

Nevertheless, I hope we all learned something

No (?)


The paper "Mechanizing Coinduction and Corecursion in Higher-order Logic" states:

"With corecursion, the case analysis is driven by the output list, rather than the input list."

That definitely makes me think of function definitions like:

  f(a,b) = pTrue <- p(a) & p(b).
  f(a,b) = qTrue <- q(a) & q(b).

But I don't see it yet...

Recursion / co-Recursion

By Matthias van der Hallen