Trees
Last time, we closed by talking about storing tree data. Our example was the family tree of George III of the United Kingdom:

We talked about using a table:
name | eye-color | mother | father |
---|---|---|---|
“George” | “green” | “Auguste” | “Frederick” |
“Auguste” | “green” | “Magdalena” | “Friedrich II” |
“Frederick” | “brown” | “” | “” |
Using this table is going to be quite inconvenient, though. How would we look
up a person’s grandparents? We’d have to do a filter-with
to find their
parents, then another filter-with
to find parents of those parents.
How about a datatype?
data AncTree: | person(name :: String, eye-color :: String, mother :: ..., father :: ...) end
What should we put as the type of the mother
and father
fields? Does
String
work?
If we use String
, we’re basically left off where we were before–we can have
a List<AncTree>
that we can use like our table. What if we did this?
data AncTree: | person(name :: String, eye-color :: String, mother :: AncTree, father :: AncTree) end
The parents of an AncTree
instance are now themselves AncTree
.
Let’s write George’s family tree:
george-tree = person("George III", "green", person("Auguste", "green", person("Magdalena", "blue", person("Sophia", "green", ...
What should we put for Sophia’s parents? Does ""
work?
We’re going to need to define a new constructor:
data AncTree: | person(name :: String, eye-color :: String, mother :: AncTree, father :: AncTree) | unknown end
So now we can say
george-tree = person("George III", "green", person("Auguste", "green", person("Magdalena", "blue", person("Sophia", "green", unknown, unknown), unknown), person("Friedrich II", "brown", unknown, unknown)), person("Frederick", "green", unknown, unknown))
Now that we’ve defined this data type, how can we use it? Let’s write a function to find a specific name in the tree:
fun in-tree(name :: String, tree :: AncTree) -> Boolean: ... where: in-tree("George III", george-tree) is true in-tree("Sophia", george-tree) is true in-tree("Doug", george-tree) is false end
How are we going to complete this function? We’re dealing with a datatype, so we
know we’re going to need cases
:
fun in-tree(name :: String, tree :: AncTree) -> Boolean: cases (AncTree) tree: | unknown => ... | person(nm, ec, mo, fa) => ... end where: in-tree("George III", george-tree) is true in-tree("Sophia", george-tree) is true in-tree("Doug", george-tree) is false end
What should we put in each case?
fun in-tree(name :: String, tree :: AncTree) -> Boolean: cases (AncTree) tree: | unknown => false | person(nm, ec, mo, fa) => (nm == name) or in-tree(name, mo) or in-tree(name, fa) end where: in-tree("George III", george-tree) is true in-tree("Sophia", george-tree) is true in-tree("Doug", george-tree) is false end
This suggests a new template for ancestor-tree processing functions (like our template for list processing functions, but for trees):
fun fun-name(..., tree :: AncTree) -> ...: cases (AncTree) tree: | unknown => ... | person(nm, ec, mo, fa) => ... fun-name(..., mo) ... fun-name(..., fa) end end