Recursion for the Masses
Total Page:16
File Type:pdf, Size:1020Kb
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 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 Recursion Schemes folds (tear down a structure) unfolds (build up a structure) algebra f a → Fix f → a ↔ coalgebra f a → a → Fix f catamorphism anamorphism 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 α) → β) paramorphism* 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 : 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: 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: 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: 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::(Functor 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 programming language 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 initial algebra doesn’t have to coincide with final coalgebra defers proof obligation to the programmer, but ensures unique iterate [Capretta et al.