From 38761a82b8202e0084cad13614f62fea3e0af051 Mon Sep 17 00:00:00 2001 From: johnW_ret <19534013+johnW-ret@users.noreply.github.com> Date: Wed, 17 Apr 2024 13:20:33 -0500 Subject: [PATCH] Updated Partial Application section Rewrote last example --- index.ipynb | 223 +++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 212 insertions(+), 11 deletions(-) diff --git a/index.ipynb b/index.ipynb index 4ab61f7..6d8dd67 100644 --- a/index.ipynb +++ b/index.ipynb @@ -1586,12 +1586,124 @@ "id": "215f5f1e", "metadata": {}, "source": [ - "This feature is called **partial application**. On its surface, it's nice for adding convenient names to helper functions. In practice, it can allow you to express complexity using simple, modular pieces:" + "This feature is called **partial application**. It can help you express complexity using simple, modular pieces:" + ] + }, + { + "cell_type": "markdown", + "id": "80ae388c", + "metadata": {}, + "source": [ + "Here we build `add` and `divide` from `combine` by passing `+` and `/` to it. This code doesn't do much anything useful though... " ] }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 174, + "metadata": { + "dotnet_interactive": { + "language": "fsharp" + }, + "polyglot_notebook": { + "kernelName": "fsharp" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "
(7, 2)
Item1
7
Item2
2
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "// y applied to x applied to f\n", + "let combine f x y = f x y\n", + "let add = combine (+) // a way to pass the + function\n", + "let divide = combine (/)\n", + "\n", + "add 3 4, divide 9 4" + ] + }, + { + "cell_type": "markdown", + "id": "2bbb29e9", + "metadata": {}, + "source": [ + "We can pass a `check` function to `combine` that can perform a check and decide whether we want to continue the computation or not." + ] + }, + { + "cell_type": "markdown", + "id": "92fbc8a2", + "metadata": {}, + "source": [ + "`f` comes first in the parameter list because we probably want will want to bind it first then later decide what type of check behavior we want." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ae36a578", + "metadata": { + "dotnet_interactive": { + "language": "fsharp" + }, + "polyglot_notebook": { + "kernelName": "fsharp" + } + }, + "outputs": [], + "source": [ + "let combine f check x y =\n", + " check f x y" + ] + }, + { + "cell_type": "markdown", + "id": "49329736", + "metadata": {}, + "source": [ + "We can build all different kinds of \"adders\" from `combine`:" + ] + }, + { + "cell_type": "code", + "execution_count": 213, "id": "265d4712", "metadata": { "dotnet_interactive": { @@ -1606,26 +1718,115 @@ "name": "stdout", "output_type": "stream", "text": [ - "8\n", - "NaN\n" + "9\n", + "Adding 5 + 6...\n", + "11\n", + "8\n" ] } ], "source": [ - "let combine f check x y =\n", - " f <|| check x y\n", + "let normalize f x y = f (abs x) (abs y)\n", "\n", - "let id2 x y = x, y\n", + "let normalizeThenAdd = combine (+) normalize\n", + "normalizeThenAdd -4 5 |> printfn \"%d\"\n", "\n", - "let add = combine (+) id2\n", - "add 3 5 |> printfn \"%d\"\n", + "let printThenAdd = combine (+) (fun f x y -> printfn \"Adding %d + %d...\" x y; f x y)\n", + "printThenAdd 5 6 |> printfn \"%d\"\n", "\n", - "let divideThen check = combine (/) check\n", - "let safeDivide = divideThen (fun x y -> if y = 0 then x, nan else x, y)\n", + "let add = combine (+) id // id is a special function that means \"do nothing\" in this context\n", + "add 3 5 |> printfn \"%d\"" + ] + }, + { + "cell_type": "markdown", + "id": "1c89cc9d", + "metadata": {}, + "source": [ + "We can build \"safe dividers\" that checks whether the denominator = 0." + ] + }, + { + "cell_type": "markdown", + "id": "1ca4528b", + "metadata": {}, + "source": [ + "`safeDivide` replaces `y` with `NaN` when `y = 0`:" + ] + }, + { + "cell_type": "code", + "execution_count": 206, + "metadata": { + "dotnet_interactive": { + "language": "fsharp" + }, + "polyglot_notebook": { + "kernelName": "fsharp" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "NaN\n" + ] + } + ], + "source": [ + "let ``convert divBy0 to NaN`` f x y =\n", + " f x (if y = 0 then nan else y)\n", "\n", + "let safeDivide = combine (/) ``convert divBy0 to NaN``\n", "safeDivide 4 0 |> printfn \"%f\"" ] }, + { + "cell_type": "markdown", + "id": "12c206eb", + "metadata": {}, + "source": [ + "`safeDivide` implicitly evaluates to a `float`, though, because `NaN` is not a valid value for `int`s. Sometimes you absolutely do want integer division, which evaluates to an `int` and ignores the remainder.\n", + "\n", + "`tryDivide` checks if `y = 0`, and if it is, it avoids doing the division altogether (by not evaluating `cont`).\n", + "> ℹ️ Note\n", + "> \n", + "> I should move this example down further to when I explain Option types, perhaps referencing this example. It is not 100% clear what is going on here without explaining option types." + ] + }, + { + "cell_type": "code", + "execution_count": 219, + "metadata": { + "dotnet_interactive": { + "language": "fsharp" + }, + "polyglot_notebook": { + "kernelName": "fsharp" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Some(4)\n", + "\n" + ] + } + ], + "source": [ + "let ``convert divBy0 to None`` cont x y =\n", + " if y = 0 then None else Some(cont x y)\n", + "\n", + "// remove this example for now and reference it when teaching the Option type\n", + "let tryDivide = combine (/) ``convert divBy0 to None``\n", + "\n", + "tryDivide 4 1 |> printfn \"%O\"\n", + "tryDivide 4 0 |> printfn \"%O\"" + ] + }, { "cell_type": "markdown", "id": "7ec9b1fa",