{
"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",
"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",
+ " a G\n",
+ " ----------\n",
+ " aa b\n",
+ "\n",
+ "So:\n",
+ "\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 == G [H] dip F\n",
+ " J″ == Q\n",
"\n",
- "Plug it in and convert to genrec.\n",
+ "Summing up:\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",
+ " 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",
"- 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",
},
{
"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"
]
},
{
},
{
"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"
]
},
{
},
{
"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!"
]
},
{
"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."
},
{
"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
},
"name": "stdout",
"output_type": "stream",
"text": [
- "[4 3 2 1 0]\n"
+ "[4 3 2 1 0]"
]
}
],
"source": [
- "J('5 range')"
+ "5 range"
]
},
{
"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"
]
},
{
"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"
]
},
{
"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
},
"name": "stdout",
"output_type": "stream",
"text": [
- "[5 4 3 2 1]\n"
+ "[5 4 3 2 1]"
]
}
],
"source": [
- "J('5 ranger')"
+ "5 ranger"
]
},
{
},
{
"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
},
"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"
]
},
{
"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"
+ "An example of a catamorphism is the sum function.\n",
+ "\n",
+ " sum == [not] 0 [uncons swap] [+] hylomorphism"
]
},
{
"cell_type": "code",
- "execution_count": 15,
+ "execution_count": 17,
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": []
+ }
+ ],
"source": [
- "define('swuncons == uncons swap') # Awkward name."
+ "clear "
]
},
{
- "cell_type": "markdown",
+ "cell_type": "code",
+ "execution_count": 18,
"metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": []
+ }
+ ],
"source": [
- "An example of a catamorphism is the sum function.\n",
- "\n",
- " sum == [not] 0 [swuncons] [+] hylomorphism"
+ "[sum [not] 0 [uncons swap] [+] hylomorphism] inscribe"
]
},
{
"cell_type": "code",
- "execution_count": 16,
+ "execution_count": 19,
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "15"
+ ]
+ }
+ ],
"source": [
- "define('sum == [not] 0 [swuncons] [+] hylomorphism')"
+ "[5 4 3 2 1] sum"
]
},
{
"cell_type": "code",
- "execution_count": 17,
+ "execution_count": 20,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
- "text": [
- "15\n"
- ]
+ "text": []
}
],
"source": [
- "J('[5 4 3 2 1] sum')"
+ "clear "
]
},
{
},
{
"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"
]
},
{
},
{
"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
},
"name": "stdout",
"output_type": "stream",
"text": [
- "120\n"
+ "120"
]
}
],
"source": [
- "J('5 factorial')"
+ "5 factorial"
]
},
{
" [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": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": []
+ }
+ ],
+ "source": [
+ "clear"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 28,
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": []
+ }
+ ],
"source": [
- "define('tails == [] swap [not] [pop] [rest dup [swons] dip] primrec')"
+ "[tails [] swap [not] [pop] [rest dup [swons] dip] tailrec] inscribe"
]
},
{
"cell_type": "code",
- "execution_count": 24,
+ "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"
]
},
{
],
"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,