{ "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "from notebook_preamble import D, DefinitionWrapper, J, V, define" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# On \"Two Exercises Found in a Book on Algorithmics\"\n", "\n", "Bird & Meertens\n", "\n", "[PDF paper available here](https://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.694.2614)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Define `scan` in terms of a reduction.\n", "\n", "> Problem I. The reduction operator `/` of APL takes some binary operator `⨁` on its left and a vector `x` of values on its right. The meaning of `⨁/x` for `x = [a b ... z]` is the value `a⨁b⨁...⨁z`. For this to be well-defined in the absence of brackets, the operation `⨁` has to be associative. Now there is another operator `\\` of APL called `scan`. Its effect is closely related to reduction in that we have:\n", "\n", " ⨁\\x = [a a⨁b a⨁b⨁c ... a⨁b⨁...⨁z]\n", "\n", "> The problem is to find some definition of `scan` as a reduction. In other words, we have to find some function `f` and an operator `⨂` so that\n", "\n", " ⨁\\x = f(a)⨂f(b)⨂...⨂f(z)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Designing the Recursive Function\n", "Ignoring the exact requirements (finding `f` and `⨂`) can we implement `scan` as a hylomorphism in Joy?\n", "\n", "Looking at the forms of hylomorphism, `H3` is the one to use:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### `H3`\n", "If the combiner and the generator both need to work on the current value then `dup` must be used, and the generator must produce one item instead of two (the b is instead the duplicate of a.)\n", "\n", "\n", " H3 == [P] [pop c] [[G] dupdip] [dip F] genrec\n", "\n", " ... a [G] dupdip [H3] dip F\n", " ... a G a [H3] dip F\n", " ... a′ a [H3] dip F\n", " ... a′ H3 a F\n", " ... a′ [G] dupdip [H3] dip F a F\n", " ... a′ G a′ [H3] dip F a F\n", " ... a″ a′ [H3] dip F a F\n", " ... a″ H3 a′ F a F\n", " ... a″ [G] dupdip [H3] dip F a′ F a F\n", " ... a″ G a″ [H3] dip F a′ F a F\n", " ... a‴ a″ [H3] dip F a′ F a F\n", " ... a‴ H3 a″ F a′ F a F\n", " ... a‴ pop c a″ F a′ F a F\n", " ... c a″ F a′ F a F\n", " ... d a′ F a F\n", " ... d′ a F\n", " ... d″" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Initial Definition\n", "We're building a list of values so this is an \"anamorphism\". (An anamorphism uses `[]` for `c` and `swons` for `F`.)\n", "\n", " scan == [P] [pop []] [[G] dupdip] [dip swons] genrec\n", "\n", "Convert to `ifte`:\n", "\n", " scan == [P] [pop []] [[G] dupdip [scan] dip swons] ifte" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "On the recursive branch `[G] dupdip` doesn't cut it:\n", "\n", " [1 2 3] [G] dupdip [scan] dip swons\n", " [1 2 3] G [1 2 3] [scan] dip swons" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Use `first`\n", "At this point, we want the copy of `[1 2 3]` to just be `1`, so we use `first`.\n", "\n", " scan == [P] [pop []] [[G] dupdip first] [dip swons] genrec\n", "\n", " [1 2 3] [G] dupdip first [scan] dip swons\n", " [1 2 3] G [1 2 3] first [scan] dip swons\n", " [1 2 3] G 1 [scan] dip swons" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### `G` applies `⨁`\n", "Now what does `G` have to do? Just apply `⨁` to the first two terms in the list.\n", "\n", " [1 2 3] G\n", " [1 2 3] [⨁] infra\n", " [1 2 3] [+] infra\n", " [3 3]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Predicate `P`\n", "Which tells us that the predicate `[P]` must guard against lists with less that two items in them:\n", "\n", " P == size 1 <=" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's see what we've got so far:\n", "\n", " scan == [P ] [pop []] [[G] dupdip first] [dip swons] genrec\n", " scan == [size 1 <=] [pop []] [[[F] infra] dupdip first] [dip swons] genrec" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Handling the Last Term\n", "This works to a point, but it throws away the last term:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "scrolled": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[1 3]\n" ] } ], "source": [ "J('[1 2 3] [size 1 <=] [pop []] [[[+] infra] dupdip first] [dip swons] genrec')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Hmm... Let's take out the `pop` for a sec..." ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "scrolled": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[6] [1 3]\n" ] } ], "source": [ "J('[1 2 3] [size 1 <=] [[]] [[[+] infra] dupdip first] [dip swons] genrec')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "That leaves the last item in our list, then it puts an empty list on the stack and `swons`'s the new terms onto that. If we leave out that empty list, they will be `swons`'d onto that list that already has the last item." ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "scrolled": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[1 3 6]\n" ] } ], "source": [ "J('[1 2 3] [size 1 <=] [] [[[+] infra] dupdip first] [dip swons] genrec')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Parameterize `⨁`\n", "So we have:\n", "\n", " [⨁] scan == [size 1 <=] [] [[[⨁] infra] dupdip first] [dip swons] genrec\n", "\n", "Trivially:\n", "\n", " == [size 1 <=] [] [[[⨁] infra] dupdip first] [dip swons] genrec\n", " == [[[⨁] infra] dupdip first] [size 1 <=] [] roll< [dip swons] genrec\n", " == [[⨁] infra] [dupdip first] cons [size 1 <=] [] roll< [dip swons] genrec\n", " == [⨁] [infra] cons [dupdip first] cons [size 1 <=] [] roll< [dip swons] genrec\n", "\n", "And so:\n", "\n", " scan == [infra] cons [dupdip first] cons [size 1 <=] [] roll< [dip swons] genrec" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "define('scan [infra] cons [dupdip first] cons [size 1 <=] [] roll< [dip swons] genrec')" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[1 3 6 10]\n" ] } ], "source": [ "J('[1 2 3 4] [+] scan')" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[1 2 6 24]\n" ] } ], "source": [ "J('[1 2 3 4] [*] scan')" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[1 1 2 2 3 3 4]\n" ] } ], "source": [ "J('[1 2 3 4 5 6 7] [neg +] scan')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Problem 2.\n", "> Define a line to be a sequence of characters not containing the newline character. It is easy to define a function `Unlines` that converts a non-empty sequence of lines into a sequence of characters by inserting newline characters between every two lines.\n", ">\n", "> Since `Unlines` is injective, the function `Lines`, which converts a sequence of characters into a sequence of lines by splitting on newline characters, can be specified as the inverse of `Unlines`.\n", ">\n", "> The problem, just as in Problem 1. is to find a definition by reduction of the function `Lines`.\n", "\n", "\n", " Unlines = uncons ['\\n' swap + +] step\n" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "'hello\\nworld'\n" ] } ], "source": [ "J('[\"hello\" \"world\"] uncons [\"\\n\" swap + +] step')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Again ignoring the actual task let's just derive `Lines`:\n", "\n", " \"abc\\nefg\\nhij\" Lines\n", " ---------------------------\n", " [\"abc\" \"efg\" \"hij\"]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Instead of `P == [size 1 <=]` we want `[\"\\n\" in]`, and for the base-case of a string with no newlines in it we want to use `unit`:\n", "\n", " Lines == [\"\\n\" in] [unit] [R0] [dip swons] genrec\n", " Lines == [\"\\n\" in] [unit] [R0 [Lines] dip swons] ifte" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Derive `R0`:\n", "\n", " \"a \\n b\" R0 [Lines] dip swons\n", " \"a \\n b\" split-at-newline swap [Lines] dip swons\n", " \"a \" \" b\" swap [Lines] dip swons\n", " \" b\" \"a \" [Lines] dip swons\n", " \" b\" Lines \"a \" swons\n", " [\" b\"] \"a \" swons\n", " [\"a \" \" b\"]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So:\n", "\n", " R0 == split-at-newline swap\n", "\n", " Lines == [\"\\n\" in] [unit] [split-at-newline swap] [dip swons] genrec" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Missing the Point?\n", "This is all good and well, but in the paper many interesting laws and properties are discussed. Am I missing the point?\n", "\n", " 0 [a b c d] [F] step == 0 [a b] [F] step 0 [c d] [F] step concat\n", "\n", "For associative function `F` and a \"unit\" element for that function, here represented by `0`.\n", "\n", "For functions that don't have a \"unit\" we can fake it (the example is given of infinity for the `min(a, b)` function.) We can also use:\n", "\n", " safe_step == [size 1 <=] [] [uncons [F] step] ifte\n", "\n", "Or:\n", "\n", " safe_step == [pop size 1 <=] [pop] [[uncons] dip step] ifte\n", "\n", " [a b c] [F] safe_step\n", " ---------------------------\n", " a [b c] [F] step\n", "\n", "To limit `F` to working on pairs of terms from its domain.\n", "\n" ] } ], "metadata": { "kernelspec": { "display_name": "Python 2", "language": "python", "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.3" } }, "nbformat": 4, "nbformat_minor": 2 }