1 # Treating Trees II: `treestep`
2 Let's consider a tree structure, similar to one described ["Why functional programming matters" by John Hughes](https://www.cs.kent.ac.uk/people/staff/dat/miranda/whyfp90.pdf), that consists of a node value followed by zero or more child trees. (The asterisk is meant to indicate the [Kleene star](https://en.wikipedia.org/wiki/Kleene_star).)
4 tree = [] | [node tree*]
6 In the spirit of `step` we are going to define a combinator `treestep` which expects a tree and three additional items: a base-case function `[B]`, and two quoted programs `[N]` and `[C]`.
8 tree [B] [N] [C] treestep
10 If the current tree node is empty then just execute `B`:
12 [] [B] [N] [C] treestep
13 ---------------------------
16 Otherwise, evaluate `N` on the node value, `map` the whole function (abbreviated here as `K`) over the child trees recursively, and then combine the result with `C`.
18 [node tree*] [B] [N] [C] treestep
19 --------------------------------------- w/ K == [B] [N] [C] treestep
20 node N [tree*] [K] map C
22 (Later on we'll experiment with making `map` part of `C` so you can use other combinators.)
24 ## Derive the recursive function.
25 We can begin to derive it by finding the `ifte` stage that `genrec` will produce.
27 K == [not] [B] [R0] [R1] genrec
28 == [not] [B] [R0 [K] R1] ifte
30 So we just have to derive `J`:
34 The behavior of `J` is to accept a (non-empty) tree node and arrive at the desired outcome.
37 ------------------------------
38 node N [tree*] [K] map C
40 So `J` will have some form like:
42 J == ... [N] ... [K] ... [C] ...
44 Let's dive in. First, unquote the node and `dip` `N`.
46 [node tree*] uncons [N] dip
50 Next, `map` `K` over the child trees and combine with `C`.
52 node N [tree*] [K] map C
53 node N [tree*] [K] map C
58 J == uncons [N] dip [K] map C
60 Plug it in and convert to `genrec`:
62 K == [not] [B] [J ] ifte
63 == [not] [B] [uncons [N] dip [K] map C] ifte
64 == [not] [B] [uncons [N] dip] [map C] genrec
66 ## Extract the givens to parameterize the program.
69 [not] [B] [uncons [N] dip] [map C] genrec
70 [B] [not] swap [uncons [N] dip] [map C] genrec
71 [B] [uncons [N] dip] [[not] swap] dip [map C] genrec
73 [B] [[N] dip] [uncons] swoncat [[not] swap] dip [map C] genrec
74 [B] [N] [dip] cons [uncons] swoncat [[not] swap] dip [map C] genrec
75 ^^^^^^^^^^^^^^^^^^^^^^^^^^^
77 Extract a couple of auxiliary definitions:
79 TS.0 == [[not] swap] dip
80 TS.1 == [dip] cons [uncons] swoncat
82 [B] [N] TS.1 TS.0 [map C] genrec
83 [B] [N] [map C] [TS.1 TS.0] dip genrec
84 [B] [N] [C] [map] swoncat [TS.1 TS.0] dip genrec
86 The givens are all to the left so we have our definition.
88 ### (alternate) Extract the givens to parameterize the program.
91 [not] [B] [uncons [N] dip] [map C] genrec
92 [not] [B] [N] [dip] cons [uncons] swoncat [map C] genrec
93 [B] [N] [not] roll> [dip] cons [uncons] swoncat [map C] genrec
94 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
100 from notebook_preamble import D, J, V, define, DefinitionWrapper
105 DefinitionWrapper.add_definitions('''
107 _treestep_0 == [[not] swap] dip
108 _treestep_1 == [dip] cons [uncons] swoncat
109 treegrind == [_treestep_1 _treestep_0] dip genrec
110 treestep == [map] swoncat treegrind
116 Consider trees, the nodes of which are integers. We can find the sum of all nodes in a tree with this function:
118 sumtree == [pop 0] [] [sum +] treestep
122 define('sumtree == [pop 0] [] [sum +] treestep')
125 Running this function on an empty tree value gives zero:
127 [] [pop 0] [] [sum +] treestep
128 ------------------------------------
133 J('[] sumtree') # Empty tree.
139 Running it on a non-empty node:
141 [n tree*] [pop 0] [] [sum +] treestep
142 n [tree*] [[pop 0] [] [sum +] treestep] map sum +
150 J('[23] sumtree') # No child trees.
158 J('[23 []] sumtree') # Child tree, empty.
166 J('[23 [2 [4]] [3]] sumtree') # Non-empty child trees.
174 J('[23 [2 [8] [9]] [3] [4 []]] sumtree') # Etc...
182 J('[23 [2 [8] [9]] [3] [4 []]] [pop 0] [] [cons sum] treestep') # Alternate "spelling".
190 J('[23 [2 [8] [9]] [3] [4 []]] [] [pop 23] [cons] treestep') # Replace each node.
193 [23 [23 [23] [23]] [23] [23 []]]
198 J('[23 [2 [8] [9]] [3] [4 []]] [] [pop 1] [cons] treestep')
201 [1 [1 [1] [1]] [1] [1 []]]
206 J('[23 [2 [8] [9]] [3] [4 []]] [] [pop 1] [cons] treestep sumtree')
214 J('[23 [2 [8] [9]] [3] [4 []]] [pop 0] [pop 1] [sum +] treestep') # Combine replace and sum into one function.
222 J('[4 [3 [] [7]]] [pop 0] [pop 1] [sum +] treestep') # Combine replace and sum into one function.
228 ## Redefining the Ordered Binary Tree in terms of `treestep`.
230 Tree = [] | [[key value] left right]
232 What kind of functions can we write for this with our `treestep`?
234 The pattern for processing a non-empty node is:
236 node N [tree*] [K] map C
238 Plugging in our BTree structure:
240 [key value] N [left right] [K] map C
243 [key value] first [left right] [K] map i
244 key [value] [left right] [K] map i
245 key [left right] [K] map i
249 This doesn't quite work:
253 J('[[3 0] [[2 0] [][]] [[9 0] [[5 0] [[4 0] [][]] [[8 0] [[6 0] [] [[7 0] [][]]][]]][]]] ["B"] [first] [i] treestep')
259 Doesn't work because `map` extracts the `first` item of whatever its mapped function produces. We have to return a list, rather than depositing our results directly on the stack.
262 [key value] N [left right] [K] map C
264 [key value] first [left right] [K] map flatten cons
265 key [left right] [K] map flatten cons
266 key [[lk] [rk] ] flatten cons
272 [] [first] [flatten cons] treestep
276 J('[[3 0] [[2 0] [] []] [[9 0] [[5 0] [[4 0] [] []] [[8 0] [[6 0] [] [[7 0] [] []]] []]] []]] [] [first] [flatten cons] treestep')
284 ### In-order traversal
291 [lk] [rk] key swons concat
297 [] [i roll< swons concat] [first] treestep
301 J('[[3 0] [[2 0] [] []] [[9 0] [[5 0] [[4 0] [] []] [[8 0] [[6 0] [] [[7 0] [] []]] []]] []]] [] [uncons pop] [i roll< swons concat] treestep')
308 The `treegrind` function doesn't include the `map` combinator, so the `[C]` function must arrange to use some combinator on the quoted recursive copy `[K]`. With this function, the pattern for processing a non-empty node is:
312 Plugging in our BTree structure:
314 [key value] N [left right] [K] C
318 J('[["key" "value"] ["left"] ["right"] ] ["B"] ["N"] ["C"] treegrind')
321 ['key' 'value'] 'N' [['left'] ['right']] [[not] ['B'] [uncons ['N'] dip] ['C'] genrec] 'C'
324 ## `treegrind` with `step`
326 Iteration through the nodes
330 J('[[3 0] [[2 0] [] []] [[9 0] [[5 0] [[4 0] [] []] [[8 0] [[6 0] [] [[7 0] [] []]] []]] []]] [pop] ["N"] [step] treegrind')
333 [3 0] 'N' [2 0] 'N' [9 0] 'N' [5 0] 'N' [4 0] 'N' [8 0] 'N' [6 0] 'N' [7 0] 'N'
340 J('0 [[3 0] [[2 0] [] []] [[9 0] [[5 0] [[4 0] [] []] [[8 0] [[6 0] [] [[7 0] [] []]] []]] []]] [pop] [first +] [step] treegrind')
346 Rebuild the tree using `map` (imitating `treestep`.)
350 J('[[3 0] [[2 0] [] []] [[9 0] [[5 0] [[4 0] [] []] [[8 0] [[6 0] [] [[7 0] [] []]] []]] []]] [] [[100 +] infra] [map cons] treegrind')
353 [[103 0] [[102 0] [] []] [[109 0] [[105 0] [[104 0] [] []] [[108 0] [[106 0] [] [[107 0] [] []]] []]] []]]
356 ## Do we have the flexibility to reimplement `Tree-get`?
359 [B] [N] [C] treegrind
361 We'll start by saying that the base-case (the key is not in the tree) is user defined, and the per-node function is just the query key literal:
363 [B] [query_key] [C] treegrind
365 This means we just have to define `C` from:
367 [key value] query_key [left right] [K] C
372 C == P [T>] [E] [T<] cmp
374 [key value] query_key [left right] [K] P [T>] [E] [T<] cmp
376 ### The predicate `P`
377 Seems pretty easy (we must preserve the value in case the keys are equal):
379 [key value] query_key [left right] [K] P
380 [key value] query_key [left right] [K] roll<
381 [key value] [left right] [K] query_key [roll< uncons swap] dip
383 [key value] [left right] [K] roll< uncons swap query_key
384 [left right] [K] [key value] uncons swap query_key
385 [left right] [K] key [value] swap query_key
386 [left right] [K] [value] key query_key
388 P == roll< [roll< uncons swap] dip
390 (Possibly with a swap at the end? Or just swap `T<` and `T>`.)
394 [left right] [K] [value] key query_key [T>] [E] [T<] cmp
396 Becomes one of these three:
398 [left right] [K] [value] T>
399 [left right] [K] [value] E
400 [left right] [K] [value] T<
406 E == roll> popop first
410 T< == pop [first] dip i
411 T> == pop [second] dip i
413 ## Putting it together
416 T> == pop [first] dip i
417 T< == pop [second] dip i
418 E == roll> popop first
419 P == roll< [roll< uncons swap] dip
421 Tree-get == [P [T>] [E] [T<] cmp] treegrind
423 To me, that seems simpler than the `genrec` version.
427 DefinitionWrapper.add_definitions('''
429 T> == pop [first] dip i
430 T< == pop [second] dip i
431 E == roll> popop first
432 P == roll< [roll< uncons swap] dip
434 Tree-get == [P [T>] [E] [T<] cmp] treegrind
443 [[3 13] [[2 12] [] []] [[9 19] [[5 15] [[4 14] [] []] [[8 18] [[6 16] [] [[7 17] [] []]] []]] []]]
457 [[3 13] [[2 12] [] []] [[9 19] [[5 15] [[4 14] [] []] [[8 18] [[6 16] [] [[7 17] [] []]] []]] []]]
459 [pop "nope"] [25] Tree-get