Grokking recursion-schemes: Part 2

In this post I’d like to talk about the second half of recusion-schemes. Previously we’d talked about catamorphisms and friends. These all focused on “destroying” a datastructure by collapsing it layer by layer.

We’re now going to talk about the opposite: anamorphisms. Anamorphisms are just like generalized versions of unfoldr.

That’s it! We define the dual to Foldable’s project, embed. This just defines how to take the datastructure that we’ve built up and stick it back into our list.

Using Anamorphisms

Now, let’s actually start writing some anamorphisms. The simplest example of an unfolding I can think of is between. between takes two boundaries and then creates a list of values between the high and the low, (low, high).

> enum 15
[2, 3, 4]
> enum 'a''c'"b"> enum FalseFalse
[False]

To make this more fun, we’ll return MyList a instead of just [a] since it’ll make it easier to show off recursion-schemes. I’ll explain how to generate [a]’s momentarily.

recursion-schemes defines the type instance for [a] with two constructor Cons and Nil that behave precisely like BCons and BNil. However, Cons and Nil are defined using some type families magic that makes them invisible in the documentation (I found them by reading the source). They exist I promise :)

Now, I said before this was just a generalized version of unfoldr, let’s look at the type of unfoldr.

Data.List.unfoldr :: (b ->Maybe (a, b)) -> b -> [a]

So unfoldr takes our seed value, b, and splits it into either a value and another seed, or nothing. Sound familiar? Look again at Cons, Cons is a value a, and the next seed b! Furthermore Nil is completely ismorphic to Nothing here.

Now ana generalizes upon unfoldr since we don’t need to represent everything as either 1 terminator, Nothing, or one builder, (a, b).

Now ana could handle the fact that we can now “build” new seeds in two ways, with RedB or BlackB!

Building Stuff Up to Tear It Down

One of the most common patterns in Haskell is to create some intermediate data structure and immediately use it.

This is kinda like smashing an anamorphism and a catamorphism together into one. This has a name: a hylomorphism, hylo in recursion-schemes.

It turns out that this is one of the most useful applications of anamorphisms!

As a fun example, Daniel Wagner blogged about how we can generate an infinite list of all rational numbers. The key to this is an infinite binary tree where each node is a rational number p/q and it’s two children are (p + q) / q and p / (p + q).

A Recap

We’ve now covered the core elements of the recursion-schemes library, but I’m not quite done with this blog series. I’m planning on one more post detailing my attempt to actually use recursion-schemes in a real project: a scheme compiler.

I think it would make the post more interesting though if the next post didn’t just include an example of “stuff I find cool”, so, if you have any particular example of cleaning up some code using recursion-schemes, please let me know! I’d love to share any and all examples I can find since that’s been the best way I’ve found to actually grok recurion-schemes.

If you’re interested in sharing, either comment or email me at jozefg [at] cmu.edu.