Update and publish catamorphism article

Danila Fedorin 11 months ago
parent ded50480a8
commit 57aecc46be
  1. 11
  2. 39

@ -99,3 +99,14 @@ invert :: BinaryTree a -> BinaryTree a
invert = cata $ \case
LeafF -> Leaf
NodeF a l r -> Node a r l
data MaybeF a b = NothingF | JustF a deriving Functor
instance Cata (Maybe a) (MaybeF a) where
out Nothing = NothingF
out (Just x) = JustF x
getOrDefault :: a -> Maybe a -> a
getOrDefault d = cata $ \case
NothingF -> d
JustF a -> a

@ -1,10 +1,17 @@
title: "Generalizing Folds in Haskell"
date: 2022-04-22T12:19:22-07:00
draft: true
tags: ["Haskell"]
Have you encountered Haskell's `foldr` function? Did you know that you can use it to express any
function on a list? What's more, there's a way to derive similar functions for
{{< sidenote "right" "positive-note" "a large class of data types in Haskell." >}}
Specifically, this is the class of <a href="https://en.wikipedia.org/wiki/Inductive_type">inductive types</a>.
{{< /sidenote >}}
This is precisely the focus of this post.
Before we get into the details, it's good to review the underlying concepts in a more familiar setting: functions.
### Recursive Functions
Let's start off with a little bit of a warmup, and take a look at a simple recursive function:
`length`. Here's a straightforward definition:
@ -338,6 +345,36 @@ Given this, here's an implementation of that `invert` function we mentioned earl
{{< codelines "Haskell" "catamorphisms/Cata.hs" 98 101 >}}
#### Degenerate Cases
Actually, the data types we consider don't have to be recursive. We can apply the same
procedure of replacing recursive occurrences in a data type's definition with a new type parameter
to `Maybe`; the only difference is that now the new parameter will not be used!
{{< codelines "Haskell" "catamorphisms/Cata.hs" 103 107 >}}
And then we can define a function on `Maybe` using `cata`:
{{< codelines "Haskell" "catamorphisms/Cata.hs" 109 112 >}}
This isn't _really_ useful, since we're still pattern matching on a type that looks
identical to `Maybe` itself. There is one reason that I bring it up, though. Remember
how `foldr` was equivalent to `cata` for `MyList`, because defining a function `MyListF a -> a` was
the same as providing a base case `a` and a "combining function" `Int -> a -> a`? Well,
defining a function `MaybeF x a -> a` is the same as providing a base case `a` (for `NothingF`)
and a handler for the contained value, `x -> a`. So we might imagine the `foldr` function for `Maybe`
to have type:
maybeFold :: a -> (x -> a) -> Maybe x -> a
This is exactly the function [`maybe` from `Data.Maybe`](https://hackage.haskell.org/package/base-! Hopefully you can follow a similar process in your head to arrive at "fold"
functions for `Either` and `Bool`. Indeed, there are functions that correspond to these data types
in the Haskell standard library, named [`either`](https://hackage.haskell.org/package/base- and [`bool`](https://hackage.haskell.org/package/base-
Much like `fold` can be used to represent any function on lists, `maybe`, `either`, and `bool` can be
used to represent any function on their corresponding data types. I think that's neat.
#### What About `Foldable`?
If you've been around the Haskell ecosystem, you may know the `Foldable` type class.
Isn't this exactly what we've been working towards here? No, not at all. Take