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