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

• Interested in 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 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 - 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] 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