Switch to Joy kernel.

This commit is contained in:
Simon Forman 2021-11-30 21:00:26 -08:00
parent fcd4c613e4
commit 384d391175
1 changed files with 375 additions and 226 deletions

View File

@ -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,