layout: post
title: “Introducing Folds”
description: “Threading state through a recursive data structure”
seriesId: “Recursive types and folds”
seriesOrder: 3

categories: [Folds, Patterns]

This post is the third in a series.

In the first post, I introduced “catamorphisms”, a way of creating functions for recursive types,
and in the second post, we created a few catamorphism implementations.

But at the end of the previous post, I noted that all the catamorphism implementations so far have had a potentially serious flaw.

In this post, we’ll look at the flaw and how to work around it, and in the process look at folds, tail-recursion and the difference between “left fold” and “right fold”.

Series contents

Here’s the contents of this series:


A flaw in our catamorphism implementation

Before we look at the flaw, let’s first review the recursive type Gift and the associated catamorphism cataGift that we created for it.

Here’s the domain:

  1. type Book = {title: string; price: decimal}
  2. type ChocolateType = Dark | Milk | SeventyPercent
  3. type Chocolate = {chocType: ChocolateType ; price: decimal}
  4. type WrappingPaperStyle =
  5. | HappyBirthday
  6. | HappyHolidays
  7. | SolidColor
  8. type Gift =
  9. | Book of Book
  10. | Chocolate of Chocolate
  11. | Wrapped of Gift * WrappingPaperStyle
  12. | Boxed of Gift
  13. | WithACard of Gift * message:string

Here are some example values that we’ll be using in this post:

  1. // A Book
  2. let wolfHall = {title="Wolf Hall"; price=20m}
  3. // A Chocolate
  4. let yummyChoc = {chocType=SeventyPercent; price=5m}
  5. // A Gift
  6. let birthdayPresent = WithACard (Wrapped (Book wolfHall, HappyBirthday), "Happy Birthday")
  7. // A Gift
  8. let christmasPresent = Wrapped (Boxed (Chocolate yummyChoc), HappyHolidays)

Here’s the catamorphism:

  1. let rec cataGift fBook fChocolate fWrapped fBox fCard gift :'r =
  2. let recurse = cataGift fBook fChocolate fWrapped fBox fCard
  3. match gift with
  4. | Book book ->
  5. fBook book
  6. | Chocolate choc ->
  7. fChocolate choc
  8. | Wrapped (gift,style) ->
  9. fWrapped (recurse gift,style)
  10. | Boxed gift ->
  11. fBox (recurse gift)
  12. | WithACard (gift,message) ->
  13. fCard (recurse gift,message)

and here is a totalCostUsingCata function built using cataGift:

  1. let totalCostUsingCata gift =
  2. let fBook (book:Book) =
  3. book.price
  4. let fChocolate (choc:Chocolate) =
  5. choc.price
  6. let fWrapped (innerCost,style) =
  7. innerCost + 0.5m
  8. let fBox innerCost =
  9. innerCost + 1.0m
  10. let fCard (innerCost,message) =
  11. innerCost + 2.0m
  12. // call the catamorphism
  13. cataGift fBook fChocolate fWrapped fBox fCard gift

What’s the flaw?

So what is wrong with this implementation? Let’s stress test it and find out!

What we’ll do is create a Box inside a Box inside a Box a very large number of times, and see what happens.

Here’s a little helper function to create nested boxes:

  1. let deeplyNestedBox depth =
  2. let rec loop depth boxSoFar =
  3. match depth with
  4. | 0 -> boxSoFar
  5. | n -> loop (n-1) (Boxed boxSoFar)
  6. loop depth (Book wolfHall)

Let’s try it to make sure it works:

  1. deeplyNestedBox 5
  2. // Boxed (Boxed (Boxed (Boxed (Boxed (Book {title = "Wolf Hall"; price = 20M})))))
  3. deeplyNestedBox 10
  4. // Boxed(Boxed(Boxed(Boxed(Boxed
  5. // (Boxed(Boxed(Boxed(Boxed(Boxed(Book {title = "Wolf Hall";price = 20M}))))))))))

Now try running totalCostUsingCata with these deeply nested boxes:

  1. deeplyNestedBox 10 |> totalCostUsingCata // OK 30.0M
  2. deeplyNestedBox 100 |> totalCostUsingCata // OK 120.0M
  3. deeplyNestedBox 1000 |> totalCostUsingCata // OK 1020.0M

So far so good.

But if we use much larger numbers, we soon run into a stack overflow exception:

  1. deeplyNestedBox 10000 |> totalCostUsingCata // Stack overflow?
  2. deeplyNestedBox 100000 |> totalCostUsingCata // Stack overflow?

The exact number which causes an error depends on the environment, available memory, and so on.
But it is a certainty that you will run into it when you start using largish numbers.

Why is this happening?

The problem with deep recursion

Recall that the definition of the cost for the Boxed case (fBox) was innerCost + 1.0m.
And what is the inner cost? That’s another box too, so we end up with a chain of calculations looking like this:

  1. innerCost + 1.0m where innerCost =
  2. innerCost2 + 1.0m where innerCost2 =
  3. innerCost3 + 1.0m where innerCost3 =
  4. innerCost4 + 1.0m where innerCost4 =
  5. ...
  6. innerCost999 + 1.0m where innerCost999 =
  7. innerCost1000 + 1.0m where innerCost1000 =
  8. book.price

In other words, innerCost1000 has to be calculated before innerCost999 can be calculated,
and 999 other inner costs have to be calculated before the top level innerCost can be calculated.

Every level is waiting for its inner cost to be calculated before doing the calculation for that level.

All these unfinished calculations are stacked up waiting for the inner one to complete. And when you have too many? Boom! Stack overflow!

The solution to stack overflows

The solution to this problem is simple. Rather than each level waiting for the inner cost to be calculated, each level calculates the cost so far, using an accumulator,
and passes that down to the next inner level. When we get to the bottom level, we have the final answer.

  1. costSoFar = 1.0m; evaluate calcInnerCost with costSoFar:
  2. costSoFar = costSoFar + 1.0m; evaluate calcInnerCost with costSoFar:
  3. costSoFar = costSoFar + 1.0m; evaluate calcInnerCost with costSoFar:
  4. costSoFar = costSoFar + 1.0m; evaluate calcInnerCost with costSoFar:
  5. ...
  6. costSoFar = costSoFar + 1.0m; evaluate calcInnerCost with costSoFar:
  7. costSoFar = costSoFar + 1.0m; evaluate calcInnerCost with costSoFar:
  8. finalCost = costSoFar + book.price // final answer

The big advantange of this approach is that all calculations at a particular level are completely finished before the next lowel level is called.
Which means that the level and its associated data can be safely discarded from the stack. Which means no stack overflow!

An implementation like this, where the higher levels can be safely discarded, is called tail recursive.

Reimplementating the totalCost function with an accumulator

Let’s rewrite the total cost function from scratch, using an accumulator called costSoFar:

  1. let rec totalCostUsingAcc costSoFar gift =
  2. match gift with
  3. | Book book ->
  4. costSoFar + book.price // final result
  5. | Chocolate choc ->
  6. costSoFar + choc.price // final result
  7. | Wrapped (innerGift,style) ->
  8. let newCostSoFar = costSoFar + 0.5m
  9. totalCostUsingAcc newCostSoFar innerGift
  10. | Boxed innerGift ->
  11. let newCostSoFar = costSoFar + 1.0m
  12. totalCostUsingAcc newCostSoFar innerGift
  13. | WithACard (innerGift,message) ->
  14. let newCostSoFar = costSoFar + 2.0m
  15. totalCostUsingAcc newCostSoFar innerGift

A few things to note:

  • The new version of the function has an extra parameter (costSoFar). We will have to provide an initial value for this (such as zero) when we call it at the top level.
  • The non-recursive cases (Book and Chocolate) are the end points. The take the cost so far and add it to their price, and then that is the final result.
  • The recursive cases calculate a new costSoFar based on the parameter that is passed in. The new costSoFar is then passed down to the next lower level,
    just as in the example above.

Let’s stress test this version:

  1. deeplyNestedBox 1000 |> totalCostUsingAcc 0.0m // OK 1020.0M
  2. deeplyNestedBox 10000 |> totalCostUsingAcc 0.0m // OK 10020.0M
  3. deeplyNestedBox 100000 |> totalCostUsingAcc 0.0m // OK 100020.0M
  4. deeplyNestedBox 1000000 |> totalCostUsingAcc 0.0m // OK 1000020.0M

Excellent. Up to one million nested levels without a hiccup.

Introducing “fold”

Now let’s apply the same design principle to the catamorphism implementation.

We’ll create a new function foldGift.
We’ll introduce an accumulator acc that we will thread through each level, and the non-recursive cases will return the final accumulator.

  1. let rec foldGift fBook fChocolate fWrapped fBox fCard acc gift :'r =
  2. let recurse = foldGift fBook fChocolate fWrapped fBox fCard
  3. match gift with
  4. | Book book ->
  5. let finalAcc = fBook acc book
  6. finalAcc // final result
  7. | Chocolate choc ->
  8. let finalAcc = fChocolate acc choc
  9. finalAcc // final result
  10. | Wrapped (innerGift,style) ->
  11. let newAcc = fWrapped acc style
  12. recurse newAcc innerGift
  13. | Boxed innerGift ->
  14. let newAcc = fBox acc
  15. recurse newAcc innerGift
  16. | WithACard (innerGift,message) ->
  17. let newAcc = fCard acc message
  18. recurse newAcc innerGift

If we look at the type signature, we can see that it is subtly different. The type of the accumulator 'a is being used everywhere now.
The only time where the final return type is used is in the two non-recursive cases (fBook and fChocolate).

  1. val foldGift :
  2. fBook:('a -> Book -> 'r) ->
  3. fChocolate:('a -> Chocolate -> 'r) ->
  4. fWrapped:('a -> WrappingPaperStyle -> 'a) ->
  5. fBox:('a -> 'a) ->
  6. fCard:('a -> string -> 'a) ->
  7. // accumulator
  8. acc:'a ->
  9. // input value
  10. gift:Gift ->
  11. // return value
  12. 'r

Let’s look at this more closely, and compare the signatures of the original catamorphism from the last post with the signatures of the new fold function.

First of all, the non-recursive cases:

  1. // original catamorphism
  2. fBook:(Book -> 'r)
  3. fChocolate:(Chocolate -> 'r)
  4. // fold
  5. fBook:('a -> Book -> 'r)
  6. fChocolate:('a -> Chocolate -> 'r)

As you can see, with “fold”, the non-recursive cases take an extra parameter (the accumulator) and return the 'r type.

This is a very important point: the type of the accumulator does not need to be the same as the return type.
We will need to take advantage of this shortly.

What about the recursive cases? How did their signature change?

  1. // original catamorphism
  2. fWrapped:('r -> WrappingPaperStyle -> 'r)
  3. fBox:('r -> 'r)
  4. // fold
  5. fWrapped:('a -> WrappingPaperStyle -> 'a)
  6. fBox:('a -> 'a)

For the recursive cases, the structure is identical but all use of the 'r type has been replaced with the 'a type.
The recursive cases do not use the 'r type at all.

Reimplementating the totalCost function using fold

Once again, we can reimplement the total cost function, but this time using the foldGift function:

  1. let totalCostUsingFold gift =
  2. let fBook costSoFar (book:Book) =
  3. costSoFar + book.price
  4. let fChocolate costSoFar (choc:Chocolate) =
  5. costSoFar + choc.price
  6. let fWrapped costSoFar style =
  7. costSoFar + 0.5m
  8. let fBox costSoFar =
  9. costSoFar + 1.0m
  10. let fCard costSoFar message =
  11. costSoFar + 2.0m
  12. // initial accumulator
  13. let initialAcc = 0m
  14. // call the fold
  15. foldGift fBook fChocolate fWrapped fBox fCard initialAcc gift

And again, we can process very large numbers of nested boxes without a stack overflow:

  1. deeplyNestedBox 100000 |> totalCostUsingFold // no problem 100020.0M
  2. deeplyNestedBox 1000000 |> totalCostUsingFold // no problem 1000020.0M

Problems with fold

So using fold solves all our problems, right?

Unfortunately, no.

Yes, there are no more stack overflows, but we have another problem now.

Reimplementation of the description function

To see what the problem is, let’s revisit the description function that we created in the first post.

The original one was not tail-recursive, so let’s make it safer and reimplement it using foldGift.

  1. let descriptionUsingFold gift =
  2. let fBook descriptionSoFar (book:Book) =
  3. sprintf "'%s' %s" book.title descriptionSoFar
  4. let fChocolate descriptionSoFar (choc:Chocolate) =
  5. sprintf "%A chocolate %s" choc.chocType descriptionSoFar
  6. let fWrapped descriptionSoFar style =
  7. sprintf "%s wrapped in %A paper" descriptionSoFar style
  8. let fBox descriptionSoFar =
  9. sprintf "%s in a box" descriptionSoFar
  10. let fCard descriptionSoFar message =
  11. sprintf "%s with a card saying '%s'" descriptionSoFar message
  12. // initial accumulator
  13. let initialAcc = ""
  14. // main call
  15. foldGift fBook fChocolate fWrapped fBox fCard initialAcc gift

Let’s see what the output is:

  1. birthdayPresent |> descriptionUsingFold
  2. // "'Wolf Hall' with a card saying 'Happy Birthday' wrapped in HappyBirthday paper"
  3. christmasPresent |> descriptionUsingFold
  4. // "SeventyPercent chocolate wrapped in HappyHolidays paper in a box"

These outputs are wrong! The order of the decorations has been mixed up.

It’s supposed to be a wrapped book with a card, not a book and a card wrapped together.
And it’s supposed to be chocolate in a box, then wrapped, not wrapped chocolate in a box!

  1. // OUTPUT: "'Wolf Hall' with a card saying 'Happy Birthday' wrapped in HappyBirthday paper"
  2. // CORRECT "'Wolf Hall' wrapped in HappyBirthday paper with a card saying 'Happy Birthday'"
  3. // OUTPUT: "SeventyPercent chocolate wrapped in HappyHolidays paper in a box"
  4. // CORRECT "SeventyPercent chocolate in a box wrapped in HappyHolidays paper"

What has gone wrong?

The answer is that the correct description for each layer depends on the description of the layer below. We can’t “pre-calculate” the description for a layer
and pass it down to the next layer using a descriptionSoFar accumulator.

But now we have a dilemma: a layer depends on information from the layer below, but we want to avoid a stack overflow.

Using functions as accumulators

Remember that the accumulator type does not have to be the same as the return type. We can use anything as an accumulator, even a function!

So what we’ll do is, rather than passing a descriptionSoFar as the accumulator, we’ll pass a function (descriptionGenerator say)
that will build the appropriate description given the value of the next layer down.

Here’s the implementation for the non-recursive cases:

  1. let fBook descriptionGenerator (book:Book) =
  2. descriptionGenerator (sprintf "'%s'" book.title)
  3. // ~~~~~~~~~~~~~~~~~~~~ <= a function as an accumulator!
  4. let fChocolate descriptionGenerator (choc:Chocolate) =
  5. descriptionGenerator (sprintf "%A chocolate" choc.chocType)

The implementation for recursive cases is a bit more complicated:

  • We are given an accumulator (descriptionGenerator) as a parameter.
  • We need to create a new accumulator (a new descriptionGenerator) to pass down to the next lower layer.
  • The input to the description generator will be all the data accumulated from the lower layers.
    We manipulate that to make a new description and then call the descriptionGenerator passed in from the higher layer.

It’s more complicated to talk about than to demonstrate, so here are implementations for two of the cases:

  1. let fWrapped descriptionGenerator style =
  2. let newDescriptionGenerator innerText =
  3. let newInnerText = sprintf "%s wrapped in %A paper" innerText style
  4. descriptionGenerator newInnerText
  5. newDescriptionGenerator
  6. let fBox descriptionGenerator =
  7. let newDescriptionGenerator innerText =
  8. let newInnerText = sprintf "%s in a box" innerText
  9. descriptionGenerator newInnerText
  10. newDescriptionGenerator

We can simplify that code a little by using a lambda directly:

  1. let fWrapped descriptionGenerator style =
  2. fun innerText ->
  3. let newInnerText = sprintf "%s wrapped in %A paper" innerText style
  4. descriptionGenerator newInnerText
  5. let fBox descriptionGenerator =
  6. fun innerText ->
  7. let newInnerText = sprintf "%s in a box" innerText
  8. descriptionGenerator newInnerText

We could continue to make it more compact using piping and other things, but I think that what we have here is a good balance between conciseness and obscurity.

Here is the entire function:

  1. let descriptionUsingFoldWithGenerator gift =
  2. let fBook descriptionGenerator (book:Book) =
  3. descriptionGenerator (sprintf "'%s'" book.title)
  4. let fChocolate descriptionGenerator (choc:Chocolate) =
  5. descriptionGenerator (sprintf "%A chocolate" choc.chocType)
  6. let fWrapped descriptionGenerator style =
  7. fun innerText ->
  8. let newInnerText = sprintf "%s wrapped in %A paper" innerText style
  9. descriptionGenerator newInnerText
  10. let fBox descriptionGenerator =
  11. fun innerText ->
  12. let newInnerText = sprintf "%s in a box" innerText
  13. descriptionGenerator newInnerText
  14. let fCard descriptionGenerator message =
  15. fun innerText ->
  16. let newInnerText = sprintf "%s with a card saying '%s'" innerText message
  17. descriptionGenerator newInnerText
  18. // initial DescriptionGenerator
  19. let initialAcc = fun innerText -> innerText
  20. // main call
  21. foldGift fBook fChocolate fWrapped fBox fCard initialAcc gift

Again, I’m using overly descriptive intermediate values to make it clear what is going on.

If we try descriptionUsingFoldWithGenerator now, we get the correct answers again:

  1. birthdayPresent |> descriptionUsingFoldWithGenerator
  2. // CORRECT "'Wolf Hall' wrapped in HappyBirthday paper with a card saying 'Happy Birthday'"
  3. christmasPresent |> descriptionUsingFoldWithGenerator
  4. // CORRECT "SeventyPercent chocolate in a box wrapped in HappyHolidays paper"

Introducing “foldback”

Now that we understand what to do, let’s make a generic version that that handles the generator function logic for us.
This one we will call “foldback”:

By the way, I’m going to use term “generator” here. In other places, it is commonly referred to as a “continuation” function, often abbreviated to just “k”.

Here’s the implementation:

  1. let rec foldbackGift fBook fChocolate fWrapped fBox fCard generator gift :'r =
  2. let recurse = foldbackGift fBook fChocolate fWrapped fBox fCard
  3. match gift with
  4. | Book book ->
  5. generator (fBook book)
  6. | Chocolate choc ->
  7. generator (fChocolate choc)
  8. | Wrapped (innerGift,style) ->
  9. let newGenerator innerVal =
  10. let newInnerVal = fWrapped innerVal style
  11. generator newInnerVal
  12. recurse newGenerator innerGift
  13. | Boxed innerGift ->
  14. let newGenerator innerVal =
  15. let newInnerVal = fBox innerVal
  16. generator newInnerVal
  17. recurse newGenerator innerGift
  18. | WithACard (innerGift,message) ->
  19. let newGenerator innerVal =
  20. let newInnerVal = fCard innerVal message
  21. generator newInnerVal
  22. recurse newGenerator innerGift

You can see that it is just like the descriptionUsingFoldWithGenerator implementation, except that we are using generic newInnerVal and generator values.

The type signatures are similar to the original catamorphism, except that every case works with 'a only now.
The only time 'r is used is in the generator function itself!

  1. val foldbackGift :
  2. fBook:(Book -> 'a) ->
  3. fChocolate:(Chocolate -> 'a) ->
  4. fWrapped:('a -> WrappingPaperStyle -> 'a) ->
  5. fBox:('a -> 'a) ->
  6. fCard:('a -> string -> 'a) ->
  7. // accumulator
  8. generator:('a -> 'r) ->
  9. // input value
  10. gift:Gift ->
  11. // return value
  12. 'r

The foldback implementation above is written from scratch. If you want a fun exercise, see if you can write foldback in terms of fold.

Let’s rewrite the description function using foldback:

  1. let descriptionUsingFoldBack gift =
  2. let fBook (book:Book) =
  3. sprintf "'%s'" book.title
  4. let fChocolate (choc:Chocolate) =
  5. sprintf "%A chocolate" choc.chocType
  6. let fWrapped innerText style =
  7. sprintf "%s wrapped in %A paper" innerText style
  8. let fBox innerText =
  9. sprintf "%s in a box" innerText
  10. let fCard innerText message =
  11. sprintf "%s with a card saying '%s'" innerText message
  12. // initial DescriptionGenerator
  13. let initialAcc = fun innerText -> innerText
  14. // main call
  15. foldbackGift fBook fChocolate fWrapped fBox fCard initialAcc gift

And the results are still correct:

  1. birthdayPresent |> descriptionUsingFoldBack
  2. // CORRECT "'Wolf Hall' wrapped in HappyBirthday paper with a card saying 'Happy Birthday'"
  3. christmasPresent |> descriptionUsingFoldBack
  4. // CORRECT "SeventyPercent chocolate in a box wrapped in HappyHolidays paper"

Comparing foldback to the original catamorphism

The implementation of descriptionUsingFoldBack is almost identical to the version in the last post that used the original catamorphism cataGift.

Here’s the version using cataGift:

  1. let descriptionUsingCata gift =
  2. let fBook (book:Book) =
  3. sprintf "'%s'" book.title
  4. let fChocolate (choc:Chocolate) =
  5. sprintf "%A chocolate" choc.chocType
  6. let fWrapped (innerText,style) =
  7. sprintf "%s wrapped in %A paper" innerText style
  8. let fBox innerText =
  9. sprintf "%s in a box" innerText
  10. let fCard (innerText,message) =
  11. sprintf "%s with a card saying '%s'" innerText message
  12. // call the catamorphism
  13. cataGift fBook fChocolate fWrapped fBox fCard gift

And here’s the version using foldbackGift:

  1. let descriptionUsingFoldBack gift =
  2. let fBook (book:Book) =
  3. sprintf "'%s'" book.title
  4. let fChocolate (choc:Chocolate) =
  5. sprintf "%A chocolate" choc.chocType
  6. let fWrapped innerText style =
  7. sprintf "%s wrapped in %A paper" innerText style
  8. let fBox innerText =
  9. sprintf "%s in a box" innerText
  10. let fCard innerText message =
  11. sprintf "%s with a card saying '%s'" innerText message
  12. // initial DescriptionGenerator
  13. let initialAcc = fun innerText -> innerText // could be replaced with id
  14. // main call
  15. foldbackGift fBook fChocolate fWrapped fBox fCard initialAcc gift

All the handler functions are basically identical. The only change is the addition of an initial generator function, which is just id in this case.

However, although the code looks the same in both cases, they differ in their recursion safety. The foldbackGift version is still tail recursive, and can handle
very large nesting depths, unlike the cataGift version.

But this implementation is not perfect either. The chain of nested functions can get very slow and generate a lot of garbage, and for this particular example, there is an even
faster way, which we’ll look at in the next post.

Changing the type signature of foldback to avoid a mixup

In foldGift the signature for fWrapped is:

  1. fWrapped:('a -> WrappingPaperStyle -> 'a)

But in foldbackGift the signature for fWrapped is:

  1. fWrapped:('a -> WrappingPaperStyle -> 'a)

Can you spot the difference? No, me neither.

The two functions are very similar, yet work very differently. In the foldGift version, the first parameter is the accumulator from the outer levels,
while in foldbackGift version, the first parameter is the accumulator from the inner levels. Quite an important distinction!

It is therefore common to change the signature of the foldBack version so that the accumulator
always comes last, while in the normal fold function, the accumulator always comes first.

  1. let rec foldbackGift fBook fChocolate fWrapped fBox fCard gift generator :'r =
  2. //swapped => ~~~~~~~~~~~~~~
  3. let recurse = foldbackGiftWithAccLast fBook fChocolate fWrapped fBox fCard
  4. match gift with
  5. | Book book ->
  6. generator (fBook book)
  7. | Chocolate choc ->
  8. generator (fChocolate choc)
  9. | Wrapped (innerGift,style) ->
  10. let newGenerator innerVal =
  11. let newInnerVal = fWrapped style innerVal
  12. //swapped => ~~~~~~~~~~~~~~
  13. generator newInnerVal
  14. recurse innerGift newGenerator
  15. //swapped => ~~~~~~~~~~~~~~~~~~~~~~
  16. | Boxed innerGift ->
  17. let newGenerator innerVal =
  18. let newInnerVal = fBox innerVal
  19. generator newInnerVal
  20. recurse innerGift newGenerator
  21. //swapped => ~~~~~~~~~~~~~~~~~~~~~~
  22. | WithACard (innerGift,message) ->
  23. let newGenerator innerVal =
  24. let newInnerVal = fCard message innerVal
  25. //swapped => ~~~~~~~~~~~~~~~~
  26. generator newInnerVal
  27. recurse innerGift newGenerator
  28. //swapped => ~~~~~~~~~~~~~~~~~~~~~~

This change shows up in the type signature. The Gift value comes before the accumulator now:

  1. val foldbackGift :
  2. fBook:(Book -> 'a) ->
  3. fChocolate:(Chocolate -> 'a) ->
  4. fWrapped:(WrappingPaperStyle -> 'a -> 'a) ->
  5. fBox:('a -> 'a) ->
  6. fCard:(string -> 'a -> 'a) ->
  7. // input value
  8. gift:Gift ->
  9. // accumulator
  10. generator:('a -> 'r) ->
  11. // return value
  12. 'r

and now we can tell the two versions apart easily.

  1. // fold
  2. fWrapped:('a -> WrappingPaperStyle -> 'a)
  3. // foldback
  4. fWrapped:(WrappingPaperStyle -> 'a -> 'a)

Rules for creating a fold

To finish up this post, let’s summarize the rules for creating a fold.

In the first post we saw that creating a catamorphism is a mechanical process that follows rules.
The same is true for creating a iterative top-down fold. The process is:

  • Create a function parameter to handle each case in the structure.
  • Add an additional parameter as an accumulator.
  • For non-recursive cases, pass the function parameter the accumulator plus all the data associated with that case.
  • For recursive cases, perform two steps:
    • First, pass the handler the accumulator plus all the data associated with that case (except the inner recursive data). The result is a new accumulator value.
    • Then, call the fold recursively on the nested value using the new accumulator value.

Note that each handler only “sees” (a) the data for that case, and (b) the accumulator passed to it from the outer level.
It does not have access to the results from the inner levels.


Summary

We’ve seen in this post how to define a tail-recursive implementation of a catamorphism, called “fold” and the reverse version “foldback”.

In the next post we’ll step back a bit and spend some time understanding what “fold” really means,
and at some guidelines for choosing between fold, foldback and cata.

We’ll then see if we can apply these rules to another domain.

The source code for this post is available at this gist.