Recursion for the Masses
Recursion for the Masses TCS Seminar WS19/20 Christoph Rauch Dec - - PowerPoint PPT Presentation
Recursion for the Masses TCS Seminar WS19/20 Christoph Rauch Dec - - PowerPoint PPT Presentation
Recursion for the Masses Recursion for the Masses TCS Seminar WS19/20 Christoph Rauch Dec 10, 2019 Recursion for the Masses Introduction of Recursion to Students Early Stage (FAU) students are more or less expected to "know" what
Recursion for the Masses
Introduction of Recursion to Students
Early Stage (FAU)
students are more or less expected to "know" what recursion is no definition at all in maths classes "Java can do it" plus a definition of what a recursive definition looks like in Java in GDA (now AuD) tail recursion, mutual recursion, divide & conquer similarly "Haskell can do it" in PFP literally recommended to use iteration whenever possible nowadays, students are blessed with ThProg!
Recursion for the Masses
First Real Contact
Theory of Programming
We learn that . . .
(inductive) data types are initial algebras to functors initiality provides unique solutions to recursive equations of a certain form; the "recursion scheme" of folds (and, in fact, the more general primitive recursion) there’s a dual concept (namely final coalgebras) for coinductive data
first examples of structured recursion not the end of the line!
Recursion for the Masses
Recursion Schemes
coElgot algebra
… may short-circuit while tearing a ⨯ g b → b; ana
Elgot algebra
… may short-circuit while building cata; a → b ∨ f a
exomorphism
???
synchromorphism
???
dynamorphism
histo; ana
chronomorphism
histo; futu
hylomorphism
cata; ana ↔
metamorphism
ana; cata folds (tear down a structure) algebra f a → Fix f → a unfolds (build up a structure) coalgebra f a → a → Fix f
catamorphism
f a → a
anamorphism
a → f a
paramorphism*
… with primitive recursion f (Fix f ⨯ a) → a
apomorphism*
… returning a branch or single level a → f (Fix f ∨ a)
zygomorphism*
… with a helper function (f b → b) → (f (b ⨯ a) → a)
histomorphism
… with prev. answers it has given f (Cofree f a) → a
prepromorphism*
… after applying a NatTrans (f a → a) → (f ↝ f)
futumorphism
… multiple levels at a time a → f (Free f a)
postpromorphism*
… before applying a NatTrans (a → f a) → (f ↝ f) refolds (build up then tear down a structure) algebra g b → (f ↝ g) → coalgebra f a → a → b reunfolds (tear down then build up a structure) coalgebra g b → (a → b) → algebra f a → Fix f → Fix g
Recursion Schemes
codynamorphism
cata; futu
Stolen from Edward Kmett’s http://comonad.com/reader/ 2009/recursion-schemes/ * This gives rise to a family of related recursion schemes, modeled in recursion-schemes with distributive law combinators
g apomorphism
(b → f b) → (a → f (b ∨ a)) These can be combined in various ways. For example, a “zygohistomorphic prepromorphism” combines the zygo, histo, and prepro aspects into a signature like (f b → b) → (f ↝ f) → (f (w (b ⨯ a)) → a) → Fix f → a
generalized
(f w ↝ w f) → (f (w α) → β)
g futumorphism
(h f ↝ f h) → (a → f (m a))
g histomorphism
(f h ↝ h f) → (f (w a) → a)
generalized
(m f ↝ f m) → (α → f (m β))
generalized
apply the generalizations for both the relevant fold and unfold
generalized
apply … both … [un]fold
- thers
mutumorphism
… can refer to each other’s results (f (a ⨯ b) → a) → (f (a ⨯ b) → b)
Recursion for the Masses
Recursion Schemes
Ban General Recursion
using general recursion carelessly = chaos (structured programming : goto) as (recursion schemes : general recursion) [Meijer et al. 2006]
Use Schemes Instead
enjoy many desirable properties (e.g. fusion, compositionality) expressive, generic, concise
Recursion for the Masses
Recursion Schemes
Ban General Recursion
using general recursion carelessly = chaos (structured programming : goto) as (recursion schemes : general recursion) [Meijer et al. 2006]
Use Schemes Instead
enjoy many desirable properties (e.g. fusion, compositionality) expressive, generic, concise a little bit scary but knowing about them can help make code more efficient and safe
Recursion for the Masses
Example: Zygomorphism I
Task:
write function with property f [v,w,x,y,z] = v - (w + (x - (y + z))) first attempt: lengthEven :: [a] -> Bool lengthEven = even . length f [] = 0 f (x:xs) = if lengthEven xs then x - f xs else x + f xs
Recursion for the Masses
Example: Zygomorphism II
First,
notice that the rhs of f needs access to f xs as well as x and xs there is a scheme for that! paramorphisms – just a fancy name for primitive recursion on inductive data types: para :: (a -> [a] -> b -> b) -> b -> [a] -> b para f z [] = z para f z (x:xs) = f x xs (para f z xs) f = para (\x xs ac -> if lengthEven xs then x - ac else x + ac) 0
Recursion for the Masses
Example: Zygomorphism III
Now . . .
lengthEven is called in each recursive call quadratic algorithm but: both lengthEven and para are catamorphisms (folds): lengthEven = cata (\x p -> not p) True para' f z = snd . cata (\x (xs, acc) -> (x:xs, f x xs acc)) ([], z)
Recursion for the Masses
Example: Zygomorphism IV
Finally . . .
there is a scheme for that! running two folds in parallel, one of which depends on the result of the other, can be done by way of a zygomorphism: zygo :: (a -> b -> b)
- > (a -> b -> c -> c)
- > b -> c -> [a] -> c
zygo f g z e = snd . cata (\x (p, q) -> (f x p, g x p q)) (z, e) f = zygo (\x p -> not p) (\x e t -> if e then x - t else x + t) True 0
Recursion for the Masses
Duals and Combinations
So far . . .
we’ve only used initial algebras there are duals for final coalgebras, of course however, there are also schemes combining the two how is that even possible?
Recursion for the Masses
Data = Codata in Hask I
Turning a Blind Eye
Haskell allows partial functions blurred distinction between data types and codata types allows people to be lazy (no pun intended) "The usual style is to write as if everything is inductive, and if it still works on infinite data, to pat ourselves on the back for using Haskell rather than ML." – Conor McBride
Recursion for the Masses
Data = Codata in Hask II
Hask,
the category of Haskell types and functions, is algebraically compact basically CPO initial algebras and final coalgebras coincide
- pens up a cheap way to develop more recursion schemes
but also necessitates some arbitrary choices and a nasty proof theory
Recursion for the Masses
Example: Hylomorphism I
The scheme
given an F-algebra a and a coalgebra c, morphisms satisfying the equation h = a ◦ Fh ◦ c the so-called hylo scheme morphisms between any two types!
The predicament
however, unique solutions are not guaranteed, even in Haskell but, canonical solutions exist: hylo :: (Functor f) => (f a -> a) -> (c -> f c) -> c -> a hylo alg coalg = cata alg . ana coalg
Recursion for the Masses
Example: Hylomorphism II
The example: Merge sort Algorithm
1 recursively divide a list into smaller lists 2 when the recursive calls return, merge the resulting sorted lists into
a bigger sorted list
Implementation
divide-and-conquer algorithms lend themselves to the hylo scheme the functor f models the shape of the recursive calls; here: a binary tree an anamorphism for a coalgebra split builds up the tree and a catamorphism for an algebra merge tears it down again
Recursion for the Masses
Merge Sort
import Data.List import Data.Functor.Foldable (hylo) import qualified Data.List.Ordered as O data Tree a b = Empty | Leaf a | Node b b deriving Functor split [] = Empty split [x] = Leaf x split xs = let (l, r) = splitAt (length xs `div` 2) xs in Node l r merge Empty = [] merge (Leaf x) = [x] merge (Node l r) = O.merge l r mergesort :: Ord a => [a] -> [a] mergesort = hylo merge split
Recursion for the Masses
Generalisations
Monads and Comonads
recall: all schemes work for arbitrary functors (and their associated data types) but there’s no self-respecting Haskell program without monads recursion-schemes takes that into consideration schemes can be lifted to the (co)Kleisli category of a (co)Monad but: requires distributive laws
Distributive Laws
given monad M and functor F, natural transformation MF → FM dually for comonads recursion-schemes offers basic laws and combinators to build new
- nes
Recursion for the Masses
Monadic Iteration
Performing Side-Effects Repeatedly
Elgot monad: monad m with iteration operator of signature (a → m(b + a)) → (a → mb) satisfying certain natural axioms every Monad in Hask is Elgot (by enrichment of Kleisli over CPO) Haskell’s loopM has the type signature above more useful/problematic in more specific settings (e.g. languages without general recursion, with hard-wired effects in the background)
Recursion for the Masses
Moving to Total Programming
Totality
enables distinction between data and codata removes arbitrary choices in terms of strictness simplifies the proof theory (e.g. e − e = 0 is not always true in Haskell, since e might be ‘⊥‘) but some recursion schemes are no longer applicable Monads are no longer automatically Elgot we need to ensure productivity in (co)recursive definitions of codata
Recursion for the Masses
Catamorphisms in Agda I
Agda
dependently typed programming language totality ensured by (quite restrictive) positivity and termination checkers
General Definition
in Haskell, data Mu f = In (f (Mu f)) is valid for any functor Agda’s positivity checker rejects this however, initial algebras for polynomial functors can be defined generically data mu_ (F : Functor) : Set where <_> : [ F ] (mu F) -> mu F
Recursion for the Masses
Catamorphisms in Agda II
Implementation
naive definition of cata fails the termination check! inlining the functor action works
- nce defined, cata can be used without having to trick the
termination checker! ... mapFold (F1 |x| F2) G a (x,y) = ( mapFold F1 G a x , mapFold F2 G a y) ...
What else can we implement?
dependent version of catamorphisms [Fu, Selinger 2018] hylomorphisms?
Recursion for the Masses
Recursive coalgebras
Coalgebra-to-algebra morphisms
hylo scheme viewed from a different point of view initial algebra doesn’t have to coincide with final coalgebra defers proof obligation to the programmer, but ensures unique iterate [Capretta et al. 2004] describe several ways to build recursive coalgebras (again, using distributive laws) stronger form: very recursive coalgebras [Capretta et al. 2004]
Recursion for the Masses
"Elgot Algebras"
"A hylomorphism that cheats"
dual of very recursive coalgebra strictly stronger than the name is actually misleading scheme is the same for the concept of iterative algebras [Milius 2005] difference: unique solutions (iterative alg.) vs. solutions satisfying axioms (Elgot alg.) doesn’t matter for Haskell, but for other languages
Recursion for the Masses
Beyond Recursion Schemes I
What if . . .
we absolutely need general recursion? example: paperfolds sequence data Stream (A : Set) : Set where _::_ : A -> Stream A -> Stream A toggle : Stream Nat toggle = 1 :: 0 :: toggle interleave : Stream Nat → Stream Nat → Stream Nat hd (interleave s t) = hd s tl (interleave s t) = interleave t (tl s) {-# NON_TERMINATING #-} paperfolds : Stream Nat paperfolds = interleave toggle paperfolds
Recursion for the Masses
Beyond Recursion Schemes II
This implementation . . .
does not go through with the termination checker in fact, the same definition with the arguments to interleave swapped is not productive
Solution
perform a more cunning analysis of guardedness e.g. [Clouston et al. 2016], guarded lambda calculus:
enriches the type system by a modality ◮ (’later’) elements of ’later’ types can not be accessed ’now’ the signature of interleave becomes StreamG Nat -> ◮ StreamG Nat -> StreamG Nat
Recursion for the Masses
Last Example
Collatz Sequence as an Elgot Algebra
import Data.Functor.Foldable collatzLength :: Int -> Int collatzLength = elgot algebra coalgebra where algebra Nil = 0 algebra (Cons _ x) = x + 1 coalgebra 1 = Left 1 coalgebra n | n `mod` 2 == 0 = Right $ Cons n (div n 2) | otherwise = Right $ Cons n (3 * n + 1) (courtesy to Vanessa McHale, http://blog.vmchale.com/article/elgot-performance)
Recursion for the Masses
Selected References I
Edward Kmett recursion-schemes: Representing common recursion patterns as higher-order functions http://hackage.haskell.org/package/recursion-schemes Erik Meijer, Maarten Fokkinga, Ross Paterson (2006) Functional Programming with Bananas, Lenses, Envelopes and Barbed Wire Venanzio Capretta, Tarmo Uustalu, Varmo Vene (2004) Recursive Coalgebras from Comonads Venanzio Capretta, Tarmo Uustalu, Varmo Vene (2009) Corecursive Algebras: A Study of General Structured Corecursion Patrick Thomson (2019) Recursion Schemes blog series https://github.com/patrickt/recschemes
Recursion for the Masses
Selected References II
Ulf Norell, James Chapman (2008) Dependently Typed Programming in Agda Stefan Milius (2005) Completely Iterative Algebras and Completely Iterative Monads Clouston, Bizjak, Grathwohl, Birkedal (2016) Programming and Reasoning with Guarded Recursion for Coinductive Types Hiroshi Nakano (2000) A Modality for Recursion Peng Fu, Peter Selinger (2018) Dependently Typed Folds for Nested Data Types https://arxiv.org/abs/1806.05230
Recursion for the Masses