<<

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 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 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

Recursion Schemes folds (tear down a structure) unfolds (build up a structure) algebra f a → Fix f → a ↔ coalgebra f a → a → Fix f f a → a a → f a generalized prepromorphism* postpromorphism* (m f ↝ f m) → (α → f (m β)) … after applying a NatTrans … before applying a NatTrans generalized (f a → a) → (f ↝ f) (a → f a) → (f ↝ f) (f w ↝ w f) → (f (w α) → β) * apomorphism* … with primitive recursion … returning a branch or single level f (Fix f ⨯ a) → a a → f (Fix f ∨ a) zygomorphism* g apomorphism … with a helper function (f b → b) → (f (b ⨯ a) → a) (b → f b) → (a → f (b ∨ a)) g histomorphism histomorphism futumorphism g futumorphism … with prev. answers it has given … multiple levels at a time (f h ↝ h f) → (f (w a) → a) (h f ↝ f h) → (a → f (m a)) f (Cofree f a) → a a → f (Free f a) refolds (build up then tear down a structure) algebra g b → (f ↝ g) → coalgebra f a → a → b hylomorphism others cata; ana dynamorphism codynamorphism generalized synchromorphism apply the generalizations for both histo; ana cata; futu the relevant fold and unfold ??? chronomorphism histo; futu exomorphism Elgot algebra coElgot algebra … may short-circuit while building … may short-circuit while tearing ??? cata; a → b ∨ f a a ⨯ g b → b; ana mutumorphism reunfolds (tear down then build up a structure) coalgebra g b → (a → b) → algebra f a → Fix f → Fix g … can refer to each other’s results (f (a ⨯ b) → a) → (f (a ⨯ b) → b) metamorphism generalized ana; cata apply … both … [un]fold

Stolen from Edward Kmett’s http://comonad.com/reader/ 2009/recursion-schemes/ These can be combined in various ways. For example, a “zygohistomorphic * This gives rise to a family of related recursion schemes, modeled in recursion-schemes with distributive law prepromorphism” combines the zygo, histo, and prepro aspects into a combinators signature like (f b → b) → (f ↝ f) → (f (w (b ⨯ a)) → a) → Fix f → a a little bit scary but knowing about them can help make code more efficient and safe

Recursion for the Masses Recursion Schemes

Ban General Recursion using general recursion carelessly = chaos (structured programming : ) 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: ZygomorphismI

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 thenx- f xs elsex+ f xs Recursion for the Masses Example: ZygomorphismII

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 parafz[]=z para f z (x:xs)= f x xs (para f z xs)

f= para (\x xs ac -> if lengthEven xs thenx- ac elsex+ ac)0 Recursion for the Masses Example: ZygomorphismIII

Now . . . lengthEven is called in each recursive call quadratic algorithm but: both lengthEven and para are (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: ZygomorphismIV

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 zygofgze= snd. cata (\x (p, q) -> (f x p, g x p q)) (z, e)

f= zygo (\x p -> not p) (\x e t -> ife thenx-t elsex+ t) True0 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 opens 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: HylomorphismI

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::( f) => (f a -> a) -> (c -> f c) ->c ->a hylo alg coalg= cata alg. ana coalg Recursion for the Masses Example: HylomorphismII

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 asO data Treeab= Empty| Leafa| Nodebb deriving Functor

split[]= Empty split [x]= Leafx split xs= let (l, r)= splitAt (length xs `div` 2) xs in Nodelr merge Empty=[] merge(Leaf x)= [x] merge(Node l r)=O.merge l r

mergesort:: Orda => [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 ones 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 AgdaI

Agda dependently typed totality ensured by (quite restrictive) positivity and termination checkers

General Definition in Haskell, data Muf= 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 AgdaII

Implementation naive definition of cata fails the termination check! inlining the functor action works once defined, cata can be used without having to trick the termination checker! ... mapFold(F1|x| F2)Ga(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 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 SchemesI

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 SchemesII

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 I (’later’) elements of ’later’ types can not be accessed ’now’ the signature of interleave becomes StreamG Nat -> I 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 coalgebra1= Left1 coalgebra n |n `mod` 2 ==0= Right$ Cons n (div n2) | otherwise= Right$ Consn(3*n+1) (courtesy to Vanessa McHale, http://blog.vmchale.com/article/elgot-performance) Recursion for the Masses Selected ReferencesI

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) 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 Patrick Thomson (2019) Recursion Schemes blog series https://github.com/patrickt/recschemes Recursion for the Masses Selected ReferencesII

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 Addendum

Elgot #-algebras we can generalize Elgot algebras by involving parametrized monads i.e. bi-functors which are monads in one of their arguments hashElgot::(Bifunctor k, forall a. Monad (k a)) => (k a a -> a) -> (x -> k x a) ->x ->a hashElgotae=h whereh=a. first h.e