Lambda Jam 2018-05-22 Amy Wong
Total Page:16
File Type:pdf, Size:1020Kb
Introduction to Recursion schemes Lambda Jam 2018-05-22 Amy Wong BRICKX [email protected] Agenda • My background • Problem in recursion • Fix point concept • Recursion patterns, aka morphisms • Using morphisms to solve different recursion problems • References of further reading • Q & A My background • Scala developer • Basic Haskell knowledge • Not familiar with Category Theory • Interested in Functional Programming Recursion is ubiquitous, however, recusive calls are also repeatedly made Example 1 - expression data Expr = Const Int | Add Expr Expr | Mul Expr Expr eval :: Expr -> Int eval (Const x) = x eval (Add e1 e2) = eval e1 + eval e2 eval (Mul e1 e2) = eval e1 * eval e2 Evaluate expression > e = Mul ( Mul (Add (Const 3) (Const 0)) (Add (Const 3) (Const 2)) ) (Const 3) > eval e > 45 Example 2 - factorial factorial :: Int -> Int factorial n | n < 1 = 1 | otherwise = n * factorial (n - 1) > factorial 6 > 720 Example 3 - merge sort mergeSort :: Ord a => [a] -> [a] mergeSort = uncurry merge . splitList where splitList xs | length xs < 2 = (xs, []) | otherwise = join (***) mergeSort . splitAt (length xs `div` 2) $ xs > mergeSort [1,2,3,3,2,1,-10] > [-10,1,1,2,2,3,3] Problem • Recursive call repeatedly made and mix with the application-specific logic • Factoring out the recursive call makes the solution simpler Factor out recursion • Recursion is iteration of nested structures • If we identify abstraction of nested structure • Iteration on abstraction is factored out as pattern • We only need to define non-recursive function for application requirement • Problem solved in simple way How? Fix point Higher-kinded type Fix, (* -> *) -> * newtype Fix f = Fix { unFix :: f (Fix f) } Fix f is nested data structure We expect f is not just a type constructor but also a functor Iterate Fix f newtype Fix f = Fix { unFix :: f (Fix f) } fmap iterate f (Fix f) f a unFix alg iterate Fix f a Replace iterate by cata alg Decouples alg from recursion pattern fmap (cata alg) f (Fix f) f a unFix alg cata alg Fix f a cata function cata :: (Functor f) => (f a -> a) -> Fix f -> a cata alg = alg . fmap (cata alg) . unfix cata is called catamorphism - fundamental recursion pattern Highlights on catamorphism cata :: (Functor f) => (f a -> a) -> Fix f -> a alg :: f a -> a is called algebra, non-recursive function for application-specific logic a is called carrier of the algebra If we solve a recursion problem using cata, we only need to define functor f and f a -> a according to application requirement Catamorphism example - expression Original ADT data Expr = Const Int | Add Expr Expr | Mul Expr Expr Re-design the ADT as functor (notice the bolded carrier type) data ExprF a = Const Int | Add a a | Mul a a deriving Functor Algebra to evaluate expression -- cata :: (Functor f) => (f a -> a) -> Fix f -> a In evaluating an expression, carrier type is Int eval :: ExprF Int -> Int eval (Const c) = c eval (Add x y) = x + y eval (Mul x y) = x * y -- cata :: (Functor f) => (f a -> a) -> Fix f -> a > e = Fix $ Mul (Fix $ Mul (Fix $ Add (Fix $ Const 3) (Fix $ Const 0)) (Fix $ Add (Fix $ Const 3) (Fix $ Const 2)) ) (Fix $ Const 3) > cata eval e > 45 Algebra to format expression -- cata :: (Functor f) => (f a -> a) -> Fix f -> a In formatting an expression, carrier type is String format :: ExprF String -> String format (Const c) = show c format (Add s1 s2) = "(" <> s1 <> " + " <> s2 <> ")" format (Mul s1 s2) = s1 <> " * " <> s2 > cata format e > "(3 + 0) * (3 + 2) * 3" But catamorphism can’t solve all problems Anamorphism – opposite of Catamorphism • Catamorphism is building up a value from a nested structure, like a piece of paper folded up to an object • Anamorphism is the opposite, like an object unfolded to a piece of paper Anamorphism signature Catamorphism cata :: (Functor f) => (f a -> a) -> Fix f -> a cata alg = alg . fmap (cata alg) . unFix Anamorphism ana :: (Functor f) => (a -> f a) -> a -> Fix f ana coalg = Fix . fmap (ana coalg) . coalg coalg :: (a -> f a) is called coalgebra Hylomorphism The composition of anamorphism followed by catamorphism hylo :: (Functor f) =>(f b -> b) -> (a -> f a) -> a -> b hylo alg coalg = cata alg . ana coalg Alternatively, eliminating intermediate nested data structure hylo alg coalg = alg . fmap (hylo alg coalg) . coalg Hylomorphism example – merge sort Original code for merge sort mergeSort :: Ord a => [a] -> [a] mergeSort = uncurry merge . splitList where splitList xs | length xs < 2 = (xs, []) | otherwise = join (***) mergeSort . splitAt (length xs `div` 2) $ xs ADT for merge sort -- (notice that bolded carrier type) data TreeF a r = Empty | Leaf a | Branch r r deriving Functor Coalgebra for anamorphism -- ana :: (Functor f) => (a -> f a) -> a -> Fix f -- TreeF a is functor, [a] is carrier type split :: Ord a => [a] -> TreeF a [a] split [] = Empty split [x] = Leaf x split xs = uncurry Branch . splitAt (length xs `div` 2) $ xs Algebra for catamorphism -- cata :: (Functor f) => (f a -> a) -> Fix f -> a merge_ :: Ord a => TreeF a [a] -> [a] merge_ Empty = [] merge_ (Leaf x) = [x] merge_ (Branch l r) = merge l r -- hylo :: (Functor f) =>(f b -> b) -> (a -> f a) -> a -> b > hylo merge split [1,2,3,3,2,1,-10] > [-10,1,1,2,2,3,3] Paramorphism cata :: (Functor f) => (f a -> a) -> Fix f -> a In catamorphism, (f a -> a) only provides the value a, to be transformed. It doesn’t provide the original structure, Fix f from which the value is built. Paramorphism address this problem para :: (Functor f) =>(f (a, Fix f) -> a)-> Fix f -> a para alg = alg . fmap (para alg &&& id) . unFix Paramorphism example Original code for factorial factorial n | n < 1 = 1 | otherwise = n * factorial (n - 1) ADT for the natural number that copes with following conditions • Number < 1 • Number >= 1 data NatF a = Zero | Succ a deriving Functor Use Catamorphism for natural number -- cata :: (Functor f) => (f a -> a) -> Fix f -> a natAlg :: NatF Int -> Int natAlg Zero = 0 natAlg (Succ n) = n + 1 Use Paramorphism for factorial number --para :: (Functor f) => (f (a, Fix f) -> a) -> Fix f -> a factAlg :: NatF (Int, Fix NatF) -> Int factAlg Zero = 1 factAlg (Succ (n, x)) = n * cata natAlg (Fix $ Succ x) > nat = Fix $ Succ $ Fix $ Succ $ Fix $ Succ $ Fix Zero > para factAlg nat > 6 Recap Catamorphism cata :: (Functor f) => (f a -> a) -> Fix f -> a Anamorphism ana :: (Functor f) => (a -> f a) -> a -> Fix f Hylomorphism hylo :: (Functor f) =>(f b -> b) -> (a -> f a) -> a -> b Paramorphism para :: (Functor f) => (f (a, Fix f) -> a) -> Fix f -> a Recursion schemes • Catamorphism, paramorphism, anamorphism and hylomorphism are fundamental morphisms • They are available from Haskell recursion schemes library • When using morphisms to solve recursion problems, we only need to consider application-specific requirement by designing ADT for functor and non-recursive function More interesting morphisms • More interesting morphisms are available from recursion schemes library • More morphisms to be invented by us • Try to use morphisms to solve recursion problems to make simple solution References • https://www.schoolofhaskell.com/user/bartosz/understanding-algebras • http://blog.sumtypeofway.com/an-introduction-to-recursion-schemes/ • https://github.com/willtim/recursion-schemes/raw/master/slides-final.pdf - • http://hackage.haskell.org/package/recursion-schemes • http://maartenfokkinga.github.io/utwente/mmf91m.pdf - Functional Programming with Bananas, Lenses, Envelopes and Barbed Wire • https://github.com/slamdata/matryoshka - recursion schemes in Scala Q & A.