diff --git a/docs/Recursion_Combinators.ipynb b/docs/Recursion_Combinators.ipynb index cd89f98..bfa97db 100644 --- a/docs/Recursion_Combinators.ipynb +++ b/docs/Recursion_Combinators.ipynb @@ -1,21 +1,20 @@ { "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "from notebook_preamble import D, DefinitionWrapper, J, V, define" - ] - }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Recursion Combinators\n", "\n", - "This article describes the `genrec` combinator, how to use it, and several generic specializations.\n", + "This article describes the `genrec` combinator and how to use it, then several generic specializations.\n", + "\n", + "## General Recursive Function\n", + "\n", + "In Joy recursive functions are defined by four quoted programs and the `genrec` combinator.\n", + "\n", + " F == [if] [then] [rec1] [rec2] genrec\n", + "\n", + "This can be thought of as transforming into an \"if..then..else\" expression using the `ifte` combinator and containing a quoted copy of itself in the \"else\" branch:\n", "\n", " [if] [then] [rec1] [rec2] genrec\n", " ---------------------------------------------------------------------\n", @@ -33,130 +32,123 @@ "the combinator are again pushed onto the stack bundled up in a quoted\n", "form. Then the rec2-part is executed, where it will find the bundled\n", "form. Typically it will then execute the bundled form, either with i or\n", - "with app2, or some other combinator.\"" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ + "with app2, or some other combinator.\"\n", + "\n", "## Designing Recursive Functions\n", - "The way to design one of these is to fix your base case and \n", - "test and then treat `R1` and `R2` as an else-part \"sandwiching\"\n", - "a quotation of the whole function.\n", + "\n", + "Fix your base case and test functions and then treat `R1` and `R2` as an else-part \"sandwiching\" a quotation of the whole function.\n", "\n", "For example, given a (general recursive) function `F`:\n", "\n", - " F == [I] [T] [R1] [R2] genrec\n", - " == [I] [T] [R1 [F] R2] ifte\n", + " F == [P] [T] [R1] [R2] genrec\n", + " == [P] [T] [R1 [F] R2] ifte\n", "\n", - "If the `[I]` predicate is false you must derive `R1` and `R2` from:\n", + "Derive `R1` and `R2` from:\n", "\n", " ... R1 [F] R2\n", "\n", - "Set the stack arguments in front and figure out what `R1` and `R2`\n", - "have to do to apply the quoted `[F]` in the proper way." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Primitive Recursive Functions\n", - "Primitive recursive functions are those where `R2 == i`.\n", + "Set the stack arguments in front and figure out what `R1` and `R2` have to do to apply the quoted `[F]` in the proper way.\n", "\n", - " P == [I] [T] [R] primrec\n", - " == [I] [T] [R [P] i] ifte\n", - " == [I] [T] [R P] ifte" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ "## [Hylomorphism](https://en.wikipedia.org/wiki/Hylomorphism_%28computer_science%29)\n", - "A [hylomorphism](https://en.wikipedia.org/wiki/Hylomorphism_%28computer_science%29) is a recursive function `H :: A -> C` that converts a value of type `A` into a value of type `C` by means of:\n", "\n", - "- A generator `G :: A -> (B, A)`\n", - "- A combiner `F :: (B, C) -> C`\n", - "- A predicate `P :: A -> Bool` to detect the base case\n", - "- A base case value `c :: C`\n", + "A [hylomorphism](https://en.wikipedia.org/wiki/Hylomorphism_%28computer_science%29) is a recursive function `H` that converts a value of type `A` into a value of type `C` by means of:\n", + "\n", + "- A generator `G` from `A` to `(B, A)`\n", + "- A combiner `Q` from `(B, C)` to `C`\n", + "- A predicate `P` from `A` to `Bool` to detect the base case\n", + "- A base case value `c` of type `C`, and\n", "- Recursive calls (zero or more); it has a \"call stack in the form of a cons list\".\n", "\n", - "It may be helpful to see this function implemented in imperative Python code." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "def hylomorphism(c, F, P, G):\n", - " '''Return a hylomorphism function H.'''\n", + "It may be helpful to see this function implemented in pseudocode (Python).\n", "\n", - " def H(a):\n", - " if P(a):\n", - " result = c\n", - " else:\n", + " def hylomorphism(c, Q, P, G):\n", + " '''Return a hylomorphism function H.'''\n", + "\n", + " def H(a):\n", + " if P(a):\n", + " return c\n", " b, aa = G(a)\n", - " result = F(b, H(aa)) # b is stored in the stack frame during recursive call to H().\n", - " return result\n", + " return Q(b, H(aa))\n", + "\n", + " return H\n", "\n", - " return H" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ "Cf. [\"Bananas, Lenses, & Barbed Wire\"](http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.41.125)\n", "\n", - "Note that during evaluation of `H()` the intermediate `b` values are stored in the Python call stack. This is what is meant by \"call stack in the form of a cons list\"." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ + "Note that during evaluation of `H()` the intermediate `b` values are stored in the Python call stack. This is what is meant by \"call stack in the form of a cons list\".\n", + "\n", "## Hylomorphism in Joy\n", + "\n", + " a H\n", + " ---------\n", + " c\n", + "\n", "We can define a combinator `hylomorphism` that will make a hylomorphism combinator `H` from constituent parts.\n", "\n", - " H == [P] c [G] [F] hylomorphism\n", + " H == [P] c [G] [Q] hylomorphism\n", "\n", "The function `H` is recursive, so we start with `ifte` and set the else-part to\n", - "some function `J` that will contain a quoted copy of `H`. (The then-part just\n", - "discards the leftover `a` and replaces it with the base case value `c`.)\n", + "some function `J` that will contain a quoted copy of `H`.\n", + "The then-part just discards the leftover `a` and replaces it with the base case value `c`:\n", "\n", " H == [P] [pop c] [J] ifte\n", "\n", "The else-part `J` gets just the argument `a` on the stack.\n", "\n", " a J\n", - " a G The first thing to do is use the generator G\n", - " aa b which produces b and a new aa\n", - " aa b [H] dip we recur with H on the new aa\n", - " aa H b F and run F on the result.\n", "\n", - "This gives us a definition for `J`.\n", + "The first thing to do is use the generator `G` which produces values `b` and a new `aa`:\n", "\n", - " J == G [H] dip F\n", + " a G\n", + " ----------\n", + " aa b\n", "\n", - "Plug it in and convert to genrec.\n", + "So:\n", "\n", - " H == [P] [pop c] [G [H] dip F] ifte\n", - " H == [P] [pop c] [G] [dip F] genrec\n", + " J == G J′\n", + "\n", + "Then we recur with `H` on `aa`:\n", + "\n", + " aa b [H] dip\n", + " ------------------\n", + " aa H b\n", + " ------------------\n", + " cc b\n", + "\n", + "So:\n", + "\n", + " J′ == [H] dip J″\n", + "\n", + "And run `Q` on the result:\n", + "\n", + " cc b Q\n", + " ------------\n", + " c\n", + "So:\n", + "\n", + " J″ == Q\n", + "\n", + "Summing up:\n", + "\n", + " J == G J′\n", + " J′ == [H] dip J″\n", + " J″ == Q\n", + "\n", + "This gives us a definition for `J`:\n", + "\n", + " J == G [H] dip Q\n", + "\n", + "Plug it in and convert to genrec:\n", + "\n", + " H == [P] [pop c] [ J ] ifte\n", + " [P] [pop c] [G [H] dip Q] ifte\n", + " [P] [pop c] [G] [dip Q] genrec\n", "\n", "This is the form of a hylomorphism in Joy, which nicely illustrates that\n", "it is a simple specialization of the general recursion combinator.\n", "\n", - " H == [P] c [G] [F] hylomorphism == [P] [pop c] [G] [dip F] genrec" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ + " [P] c [G] [Q] hylomorphism\n", + " [P] [pop c] [G] [dip Q] genrec\n", + "\n", "## Derivation of `hylomorphism` combinator\n", "\n", "Now we just need to derive a definition that builds the `genrec` arguments\n", @@ -172,8 +164,6 @@ "- Use `unit` to dequote `c`.\n", "- Use `dipd` to untangle `[unit [pop] swoncat]` from the givens.\n", "\n", - "So:\n", - "\n", " H == [P] [pop c] [G] [dip F] genrec\n", " [P] [c] [pop] swoncat [G] [F] [dip] swoncat genrec\n", " [P] c unit [pop] swoncat [G] [F] [dip] swoncat genrec\n", @@ -187,11 +177,17 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 1, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [] + } + ], "source": [ - "define('hylomorphism == [unit [pop] swoncat] dipd [dip] swoncat genrec')" + "[hylomorphism [unit [pop] swoncat] dipd [dip] swoncat genrec] inscribe" ] }, { @@ -211,11 +207,17 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 2, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [] + } + ], "source": [ - "define('triangular_number == [1 <=] 0 [-- dup] [+] hylomorphism')" + "[triangular_number [1 <=] 0 [-- dup] [+] hylomorphism] inscribe" ] }, { @@ -227,36 +229,45 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "10\n" + "10" ] } ], "source": [ - "J('5 triangular_number')" + "5 triangular_number" ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "[0 0 1 3 6 10 15]\n" + "[0 0 1 3 6 10 15 21]" ] } ], "source": [ - "J('[0 1 2 3 4 5 6] [triangular_number] map')" + "clear\n", + "\n", + "[0 1 2 3 4 5 6 7] [triangular_number] map" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Neat!" ] }, { @@ -407,6 +418,7 @@ "metadata": {}, "source": [ "### `range` et. al.\n", + "\n", "An example of an anamorphism is the `range` function which generates the list of integers from 0 to *n* - 1 given *n*.\n", "\n", "Each of the above variations can be used to make four slightly different `range` functions." @@ -423,16 +435,37 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 5, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [] + } + ], "source": [ - "define('range == [0 <=] [] [-- dup] [swons] hylomorphism')" + "clear " ] }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [] + } + ], + "source": [ + "[range [0 <=] [] [-- dup] [swons] hylomorphism] inscribe" + ] + }, + { + "cell_type": "code", + "execution_count": 7, "metadata": { "scrolled": false }, @@ -441,12 +474,12 @@ "name": "stdout", "output_type": "stream", "text": [ - "[4 3 2 1 0]\n" + "[4 3 2 1 0]" ] } ], "source": [ - "J('5 range')" + "5 range" ] }, { @@ -454,17 +487,38 @@ "metadata": {}, "source": [ "#### `range` with `H2`\n", - " H2 == c swap [P] [pop] [G [F] dip] primrec\n", - " == [] swap [0 <=] [pop] [-- dup [swons] dip] primrec" + " H2 == c swap [P] [pop] [G [F] dip] tailrec\n", + " == [] swap [0 <=] [pop] [-- dup [swons] dip] tailrec" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [] + } + ], + "source": [ + "clear " ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [] + } + ], "source": [ - "define('range_reverse == [] swap [0 <=] [pop] [-- dup [swons] dip] primrec')" + "[range_reverse [] swap [0 <=] [pop] [-- dup [swons] dip] tailrec] inscribe" ] }, { @@ -478,12 +532,12 @@ "name": "stdout", "output_type": "stream", "text": [ - "[0 1 2 3 4]\n" + "[0 1 2 3 4]" ] } ], "source": [ - "J('5 range_reverse')" + "5 range_reverse" ] }, { @@ -499,14 +553,35 @@ "cell_type": "code", "execution_count": 11, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [] + } + ], "source": [ - "define('ranger == [0 <=] [pop []] [[--] dupdip] [dip swons] genrec')" + "clear " ] }, { "cell_type": "code", "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [] + } + ], + "source": [ + "[ranger [0 <=] [pop []] [[--] dupdip] [dip swons] genrec] inscribe" + ] + }, + { + "cell_type": "code", + "execution_count": 13, "metadata": { "scrolled": true }, @@ -515,12 +590,12 @@ "name": "stdout", "output_type": "stream", "text": [ - "[5 4 3 2 1]\n" + "[5 4 3 2 1]" ] } ], "source": [ - "J('5 ranger')" + "5 ranger" ] }, { @@ -534,16 +609,37 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 14, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [] + } + ], "source": [ - "define('ranger_reverse == [] swap [0 <=] [pop] [[swons] dupdip --] primrec')" + "clear " ] }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [] + } + ], + "source": [ + "[ranger_reverse [] swap [0 <=] [pop] [[swons] dupdip --] tailrec] inscribe" + ] + }, + { + "cell_type": "code", + "execution_count": 16, "metadata": { "scrolled": true }, @@ -552,19 +648,12 @@ "name": "stdout", "output_type": "stream", "text": [ - "[1 2 3 4 5]\n" + "[1 2 3 4 5]" ] } ], "source": [ - "J('5 ranger_reverse')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Hopefully this illustrates the workings of the variations. For more insight you can run the cells using the `V()` function instead of the `J()` function to get a trace of the Joy evaluation." + "5 ranger_reverse" ] }, { @@ -572,54 +661,77 @@ "metadata": {}, "source": [ "## Catamorphism\n", - "A catamorphism can be defined as a hylomorphism that uses `[uncons swap]` for `[G]`\n", - "and `[[] =]` (or just `[not]`) for the predicate `[P]`. A catamorphic function tears down a list term-by-term and makes some new value.\n", + "A catamorphic function tears down a list term-by-term and makes some new value.\n", + "It can be defined as a hylomorphism that uses `[uncons swap]` for `[G]`\n", + "and `[[] =]` (or just `[not]`) for the predicate `[P]`.\n", + "\n", + " C == [not] c [uncons swap] [F] hylomorphism\n", "\n", - " C == [not] c [uncons swap] [F] hylomorphism" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [], - "source": [ - "define('swuncons == uncons swap') # Awkward name." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ "An example of a catamorphism is the sum function.\n", "\n", - " sum == [not] 0 [swuncons] [+] hylomorphism" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [], - "source": [ - "define('sum == [not] 0 [swuncons] [+] hylomorphism')" + " sum == [not] 0 [uncons swap] [+] hylomorphism" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [] + } + ], + "source": [ + "clear " + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [] + } + ], + "source": [ + "[sum [not] 0 [uncons swap] [+] hylomorphism] inscribe" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "15\n" + "15" ] } ], "source": [ - "J('[5 4 3 2 1] sum')" + "[5 4 3 2 1] sum" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [] + } + ], + "source": [ + "clear " ] }, { @@ -632,64 +744,76 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 21, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ + "\n", + "==== Help on step ====\n", + "\n", "Run a quoted program on each item in a sequence.\n", "::\n", "\n", - " ... [] [Q] . step\n", - " -----------------------\n", - " ... .\n", + " ... [] [Q] . step\n", + " -----------------------\n", + " ... .\n", "\n", "\n", " ... [a] [Q] . step\n", " ------------------------\n", - " ... a . Q\n", + " ... a . Q\n", "\n", "\n", - " ... [a b c] [Q] . step\n", - " ----------------------------------------\n", - " ... a . Q [b c] [Q] step\n", + " ... [a b c] [Q] . step\n", + " ----------------------------------------\n", + " ... a . Q [b c] [Q] step\n", "\n", "The step combinator executes the quotation on each member of the list\n", "on top of the stack.\n", + "\n", + "---- end (step)\n", + "\n", "\n" ] } ], "source": [ - "J('[step] help')" + "[step] help" ] }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 22, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [] + } + ], "source": [ - "define('sum == 0 swap [+] step')" + "[sum 0 swap [+] step] inscribe" ] }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 23, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "15\n" + "15" ] } ], "source": [ - "J('[5 4 3 2 1] sum')" + "[5 4 3 2 1] sum" ] }, { @@ -712,16 +836,37 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 24, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [] + } + ], "source": [ - "define('factorial == 1 swap [1 <=] [pop] [[*] dupdip --] primrec')" + "clear" ] }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [] + } + ], + "source": [ + "[factorial 1 swap [1 <=] [pop] [[*] dupdip --] tailrec] inscribe" + ] + }, + { + "cell_type": "code", + "execution_count": 26, "metadata": { "scrolled": false }, @@ -730,12 +875,12 @@ "name": "stdout", "output_type": "stream", "text": [ - "120\n" + "120" ] } ], "source": [ - "J('5 factorial')" + "5 factorial" ] }, { @@ -748,54 +893,65 @@ " [1 2 3] tails\n", " --------------------\n", " [[] [3] [2 3]]\n", - " " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can build as we go, and we want `F` to run after `G`, so we use pattern `H2`:\n", + " \n", + "\n", + "We can build as we go, and we want `Q` to run after `G`, so we use pattern `H2`:\n", + "\n", + " H2 == c swap [P] [pop] [G [Q] dip] tailrec\n", "\n", - " H2 == c swap [P] [pop] [G [F] dip] primrec" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ "We would use:\n", "\n", " c == []\n", - " F == swons\n", + " Q == swons\n", " G == rest dup\n", " P == not" ] }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 27, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [] + } + ], "source": [ - "define('tails == [] swap [not] [pop] [rest dup [swons] dip] primrec')" + "clear" ] }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 28, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [] + } + ], + "source": [ + "[tails [] swap [not] [pop] [rest dup [swons] dip] tailrec] inscribe" + ] + }, + { + "cell_type": "code", + "execution_count": 29, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "[[] [3] [2 3]]\n" + "[[] [3] [2 3]]" ] } ], "source": [ - "J('[1 2 3] tails')" + "[1 2 3] tails" ] }, { @@ -837,21 +993,14 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 2", - "language": "python", - "name": "python2" + "display_name": "Joypy", + "language": "", + "name": "thun" }, "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.15" + "file_extension": ".joy", + "mimetype": "text/plain", + "name": "Joy" } }, "nbformat": 4,