{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Treating Trees II\n", "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 a sequence of zero or more child trees. (The asterisk is meant to indicate the [Kleene star](https://en.wikipedia.org/wiki/Kleene_star).)\n", "\n", " tree = [] | [node tree*]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## `treestep`\n", "In the spirit of `step` we are going to define a combinator `treestep` which expects a tree and three additional items: a base-case value `z`, and two quoted programs `[N]` and `[C]`.\n", "\n", " tree z [N] [C] treestep" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The base-case value `z` is of some type `Z`, the `[N]` function is executed per-node and should accept the node value (whatever it is) and return a value of type `A` (which can be `Z`), and the `[C]` function should expect an `A` and a list of `Z` and return a value of type `Z`:\n", "\n", " z :: Z\n", " N :: node -> A\n", " C :: A [Z*] -> Z" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If the current tree node is empty then just leave `z` on the stack in lieu:\n", "\n", " [] z [N] [C] treestep\n", " ---------------------------\n", " z" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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`.\n", "\n", " [node tree*] z [N] [C] treestep\n", " --------------------------------------- w/ K == z [N] [C] treestep\n", " node N [tree*] [K] map C" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Derive the recursive form.\n", "We can begin to derive it by finding the `ifte` stage that `genrec` will produce. The predicate and base-case functions are trivial, so we just have to derive `J`.\n", "\n", " K == [not] [pop z] [J] ifte" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The behavior of `J` is to accept a (non-empty) tree node and arrive at the desired outcome.\n", "\n", " [node tree*] J\n", " ------------------------------\n", " node N [tree*] [K] map C" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So `J` will have some form like:\n", "\n", " J == .. [N] .. [K] .. [C] .." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's dive in. First, unquote the node and `dip` `N`.\n", "\n", " [node tree*] uncons [N] dip\n", " node [tree*] [N] dip\n", " node N [tree*]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next, `map` `K` over the child trees and combine with `C`.\n", "\n", " node N [tree*] [K] map C\n", " node N [tree*] [K] map C\n", " node N [K.tree*] C" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So:\n", "\n", " J == uncons [N] dip [K] map C" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Plug it in and convert to `genrec`:\n", "\n", " K == [not] [pop z] [J ] ifte\n", " K == [not] [pop z] [uncons [N] dip [K] map C] ifte\n", " K == [not] [pop z] [uncons [N] dip] [map C] genrec" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Extract the givens to parameterize the program.\n", "Working backwards:\n", "\n", " [not] [pop z] [uncons [N] dip] [map C] genrec\n", " [not] [z] [pop] swoncat [uncons [N] dip] [map C] genrec\n", " [not] z unit [pop] swoncat [uncons [N] dip] [map C] genrec\n", " z [not] swap unit [pop] swoncat [uncons [N] dip] [map C] genrec\n", " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " TS0 == [not] swap unit [pop] swoncat" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " z TS0 [uncons [N] dip] [map C] genrec\n", " z [uncons [N] dip] [TS0] dip [map C] genrec\n", " z [[N] dip] [uncons] swoncat [TS0] dip [map C] genrec\n", " z [N] [dip] cons [uncons] swoncat [TS0] dip [map C] genrec\n", " ^^^^^^^^^^^^^^^^^^^^^^^^^^^" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " TS1 == [dip] cons [uncons] swoncat" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " z [N] TS1 [TS0] dip [map C] genrec\n", " z [N] [map C] [TS1 [TS0] dip] dip genrec\n", " z [N] [C] [map] swoncat [TS1 [TS0] dip] dip genrec\n", "\n", "The givens are all to the left so we have our definition." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Define `treestep`" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [], "source": [ "from notebook_preamble import D, J, V, define, DefinitionWrapper" ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [], "source": [ "DefinitionWrapper.add_definitions('''\n", "\n", " _treestep_0 == [not] swap unit [pop] swoncat\n", " _treestep_1 == [dip] cons [uncons] swoncat\n", " treegrind == [_treestep_1 [_treestep_0] dip] dip genrec\n", " treestep == [map] swoncat treegrind\n", "\n", "''', D)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Examples\n", "Consider trees, the nodes of which are integers. We can find the sum of all nodes in a tree with this function:\n", "\n", " sumtree == 0 [] [sum +] treestep" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Running this function on an empty tree value gives zero:\n", "\n", " [] 0 [N] [C] treestep\n", " ---------------------------\n", " 0" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Running it on a non-empty node:\n", "\n", " [n tree*] 0 [] [sum +] treestep\n", " n [tree*] [0 [] [sum +] treestep] map sum +\n", " n [ ... ] sum +\n", " n m +\n", " n+m\n" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [], "source": [ "define('sumtree == 0 [] [sum +] treestep')" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0\n" ] } ], "source": [ "J('[] sumtree') # Empty tree." ] }, { "cell_type": "code", "execution_count": 21, "metadata": { "scrolled": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "23\n" ] } ], "source": [ "J('[23] sumtree') # No child trees." ] }, { "cell_type": "code", "execution_count": 22, "metadata": { "scrolled": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "23\n" ] } ], "source": [ "J('[23 []] sumtree') # Child tree, empty." ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "32\n" ] } ], "source": [ "J('[23 [2 [4]] [3]] sumtree') # Non-empty child trees." ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "49\n" ] } ], "source": [ "J('[23 [2 [8] [9]] [3] [4 []]] sumtree') # Etc..." ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "49\n" ] } ], "source": [ "J('[23 [2 [8] [9]] [3] [4 []]] 0 [] [cons sum] treestep') # Alternate \"spelling\"." ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[23 [23 [23] [23]] [23] [23 []]]\n" ] } ], "source": [ "J('[23 [2 [8] [9]] [3] [4 []]] [] [pop 23] [cons] treestep') # Replace each node." ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[1 [1 [1] [1]] [1] [1 []]]\n" ] } ], "source": [ "J('[23 [2 [8] [9]] [3] [4 []]] [] [pop 1] [cons] treestep')" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "6\n" ] } ], "source": [ "J('[23 [2 [8] [9]] [3] [4 []]] [] [pop 1] [cons] treestep sumtree')" ] }, { "cell_type": "code", "execution_count": 29, "metadata": { "scrolled": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "6\n" ] } ], "source": [ "J('[23 [2 [8] [9]] [3] [4 []]] 0 [pop 1] [sum +] treestep') # Combine replace and sum into one function." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Redefining our BTree in terms of this form.\n", "\n", " BTree = [] | [[key value] left right]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "What kind of functions can we write for this with our `treestep`?\n", "\n", "The pattern for processing a non-empty node is:\n", "\n", " node N [tree*] [K] map C\n", "\n", "Plugging in our BTree structure:\n", "\n", " [key value] N [left right] [K] map C" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Traversal\n", " [key value] uncons pop [left right] [K] map i\n", " key [value] pop [left right] [K] map i\n", " key [left right] [K] map i\n", " key [lkey rkey ] i\n", " key lkey rkey" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This doesn't quite work:" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "3 23 23\n" ] } ], "source": [ "J('[[3 0] [[2 0] [][]] [[9 0] [[5 0] [[4 0] [][]] [[8 0] [[6 0] [] [[7 0] [][]]][]]][]]] 23 [uncons pop] [i] treestep')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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.\n", "\n", "\n", " [key value] N [left right] [K] map C\n", "\n", " [key value] first [left right] [K] map flatten cons\n", " key [left right] [K] map flatten cons\n", " key [[lk] [rk] ] flatten cons\n", " key [ lk rk ] cons\n", " [key lk rk ]\n", "\n", "So:\n", "\n", " [] [flatten cons] [first] treestep" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "scrolled": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[3 2 9 5 4 8 6 7]\n" ] } ], "source": [ "J('[[3 0] [[2 0] [] []] [[9 0] [[5 0] [[4 0] [] []] [[8 0] [[6 0] [] [[7 0] [] []]] []]] []]] [] [first] [flatten cons] treestep')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "There we go." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### In-order traversal with `treestep`.\n", "\n", "From here:\n", "\n", " key [[lk] [rk]] C\n", " key [[lk] [rk]] i\n", " key [lk] [rk] roll<\n", " [lk] [rk] key swons concat\n", " [lk] [key rk] concat\n", " [lk key rk]\n", "\n", "So:\n", "\n", " [] [i roll< swons concat] [first] treestep" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[2 3 4 5 6 7 8 9]\n" ] } ], "source": [ "J('[[3 0] [[2 0] [] []] [[9 0] [[5 0] [[4 0] [] []] [[8 0] [[6 0] [] [[7 0] [] []]] []]] []]] [] [uncons pop] [i roll< swons concat] treestep')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## with `treegrind`?\n", "What kind of functions can we write for this with our `treegrind`?\n", "\n", "The pattern for processing a non-empty node is:\n", "\n", " node N [tree*] [K] C\n", "\n", "Plugging in our BTree structure:\n", "\n", " [key value] N [left right] [K] C" ] }, { "cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "23 'N' [[44] [18]] [[not] [pop 'z'] [uncons ['N'] dip] ['C'] genrec] 'C'\n" ] } ], "source": [ "J('[23 [44] [18] ] \"z\" [\"N\"] [\"C\"] treegrind')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[3 0] 'N' [2 0] 'N' 23 23 [9 0] 'N' [5 0] 'N' [4 0] 'N' 23 23 [8 0] 'N' [6 0] 'N' 23 [7 0] 'N' 23 23 23 23\n" ] } ], "source": [ "J('[[3 0] [[2 0] [] []] [[9 0] [[5 0] [[4 0] [] []] [[8 0] [[6 0] [] [[7 0] [] []]] []]] []]] 23 [\"N\"] [step] treegrind')" ] }, { "cell_type": "code", "execution_count": 36, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0 3 2 23 23 9 5 4 23 23 8 6 23 7 23 23 23 23\n" ] } ], "source": [ "J('0 [[3 0] [[2 0] [] []] [[9 0] [[5 0] [[4 0] [] []] [[8 0] [[6 0] [] [[7 0] [] []]] []]] []]] 23 [first ] [step] treegrind')" ] }, { "cell_type": "code", "execution_count": 39, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0 [3 [2 [] []] [9 [5 [4 [] []] [8 [6 [] [7 [] []]] []]] []]]\n" ] } ], "source": [ "J('0 [[3 0] [[2 0] [] []] [[9 0] [[5 0] [[4 0] [] []] [[8 0] [[6 0] [] [[7 0] [] []]] []]] []]] [] [first ] [map cons] treegrind')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 2", "language": "python", "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.12" } }, "nbformat": 4, "nbformat_minor": 2 }