This has been done a million times before, but if you haven’t seen it, it’s pretty neat. Let’s say you have a simple recursive data structure like a binary tree:
Now say you want to traverse the nodes, in “preorder.” So you write a traversal function. It folds a binary operator f through the values in the tree, starting with an initial accumulator value of z. First, it visits the current node, then the left subtree, and finally the right subtree, combining each value it encounters with the current accumulator value to get the next accumulator value. The final accumulator value is the result.
Spelling out the steps, it looks like this:
But if you wanted an “inorder” traversal instead, you’d write a slightly different function, visiting the left subtree before the current node:
To see how the two traversal functions work, let’s use both to flatten the 3-node tree t3 we defined earlier.
But now you start thinking about post-order traversal. Do you want to write a third traversal function that’s almost the same as the first two?
So you go back to the inorder traversal and start looking real hard at that let expression. Can you rewrite it? Yes:
And now you’ve got it. Just by reordering the applications of (go l), (f v), and (go r), you can control the traversal.
Which leads to a general traversal function that lets some other function control that ordering:
Doesn’t that look beautiful?
Now you can just spell out your desired traversals:
A test drive:
And you’re happy.
But can you go further? What if your trees are binary search trees and you just want to find the minimum or maximum value? Can you just traverse to the left or right?
Isn’t that neat?