Recursion / Co-recursion
Matthias van der Hallen
The main idea is that co-recursion is the dual of recursion;
but in what sense?
Recursion
Co-recursion
- 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 https://bit.ly/3ber6zv
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 (?)
Although...
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
Recursion / co-Recursion
- 810