I am fascinated by the approach used in this blog post to traverse a rose tree a.k.a multiway tree a.k.a n-ary tree using CPS.
Here is my code, with type annotations removed and names changed, which I did while trying to understand the technique:
type 'a Tree = Node of 'a * 'a Tree list | Leaf of 'a
let rec reduce recCalls cont =
match recCalls with
| [] -> [] |> cont
| findMaxCall :: pendingCalls ->
findMaxCall (fun maxAtNode ->
reduce pendingCalls (fun maxVals -> maxAtNode :: maxVals |> cont))
let findMaxOf (roseTree : int Tree) =
let rec findMax tr cont =
match tr with
| Leaf i -> i |> cont
| Node (i, chld) ->
let recCalls = chld |> List.map findMax
reduce recCalls (fun maxVals -> List.max (i :: maxVals) |> cont)
findMax roseTree id
// test it
let FindMaxOfRoseTree =
let t = Node (1, [ Leaf 2; Leaf 3 ])
let maxOf = findMaxOf t //will be 3
maxOf
My problem is, I find this approach hard to follow. The mutual recursion (assuming that's the right term) is really clever to my simpleton brain, but I get lost while trying to understand how it works, even when using simple examples and writing down steps manually etc.
I am in need of using CPS with Rose trees, and I'll be doing the kind of traversals that require a CPS, because just like this example, computing results based on my my tree nodes require that children of the nodes are computed first. In any case, I do like CPS and I'd like to improve my understanding of it.
So my question is: Is there an alternative way of implementing CPS on rose trees which I may manage to better follow understand? Is there a way to refactor the above code which may make it easier to follow (eliminating the mutual recursion?)
If there is a name for the above approach, or some resources/books I can read to understand it better, hints are also most welcome.
CPS can definitely be confusing, but there are some things you can do to simplify this code:
Leafcase from your type because it's redundant. A leaf is just aNodewith an empty list of children.First, let's define the continuation monad:
Using this builder, we can define a general-purpose CPS
reducefunction that combines a list of "incomplete" computations into a single incomplete computation (where an incomplete computation is any function that takes a continuation of type't -> 'uand uses it to produce a value of type'u).I think this is certainly clearer, but it might seem like magic. The key to understanding
let! x = ffor this builder is thatxis the value passed tof's implied continuation. This allows us to get rid of lots of lambdas and nested parens.Now we're ready to work with rose trees. Here's the simplified type definition:
Finding the maximum value in a tree now looks like this:
Note that there's no mutual recursion here. Both
reduceandfindMaxare self-recursive, butreducedoesn't callfindMaxand doesn't know anything about rose trees.You can test the refactored code like this:
For convenience, I created a gist containing all the code.