From 78288e24af8e527852d1c4f5419f371a01ec71e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20=C3=81ngel=20M=C3=A9ndez=20Gort?= Date: Tue, 21 May 2024 17:56:50 +0200 Subject: [PATCH] wip bottom-up --- README.md | 11 +- docs/index.ipynb | 3 +- docs/r0b0t.ipynb | 280 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 288 insertions(+), 6 deletions(-) create mode 100644 docs/r0b0t.ipynb diff --git a/README.md b/README.md index 02a0622..06477d7 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,9 @@ patterns that traditional imperative programming hides. I was delighted to disco Another surprise is that F# is a great candidate to explore functional data structures, which seem could be a good update to the excellent work by [Chris Okasaki](https://en.wikipedia.org/wiki/Chris_Okasaki#Purely_functional_data_structures) -- [Breadth-First Search](./docs/bfs.ipynb) -- [Queue](./docs/queue.ipynb) -- [Pattern matching nesting reduction](./docs/pattern_matching_nesting_reduction.ipynb) -- [Fixed points](./docs/fixed_points.ipynb) -- [Exception vs Result](./docs/exception_vs_result.ipynb) \ No newline at end of file +- [Breadth-First Search](./bfs.ipynb) +- [Queue](./queue.ipynb) +- [Pattern matching nesting reduction](./pattern_matching_nesting_reduction.ipynb) +- [Fixed points](./fixed_points.ipynb) +- [Exception vs Result](./exception_vs_result.ipynb) +- [Bottom-up approach to devise a structure](./r0b0t.ipynb) \ No newline at end of file diff --git a/docs/index.ipynb b/docs/index.ipynb index cf99ea3..adba352 100644 --- a/docs/index.ipynb +++ b/docs/index.ipynb @@ -16,7 +16,8 @@ "- [Queue](./queue.ipynb)\n", "- [Pattern matching nesting reduction](./pattern_matching_nesting_reduction.ipynb)\n", "- [Fixed points](./fixed_points.ipynb)\n", - "- [Exception vs Result](./exception_vs_result.ipynb)" + "- [Exception vs Result](./exception_vs_result.ipynb)\n", + "- [Bottom-up approach to devise a structure](./r0b0t.ipynb)" ] } ], diff --git a/docs/r0b0t.ipynb b/docs/r0b0t.ipynb new file mode 100644 index 0000000..3e9e16a --- /dev/null +++ b/docs/r0b0t.ipynb @@ -0,0 +1,280 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Streaming data from APIs" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A module is a group of elements whose interactions can be contained, abstracted and hidden under a small interface compared to the interface that would be the result exposing their full domain.\n", + "\n", + "The below function, `readAnswer` contains logic for consuming an asynrchronous stream of strings, and sending it to a consumer until there's no more strings to consume. Loops are one of the typical domains that can be contained and exposed through a small interface.\n", + "\n", + "Another of the guiding principiles of this design is to expose the minimal interface of existing abstractions. That's the case of `MailboxProcessor`, which is used in `readAnswer` just an `unit -> Async` (type `ReadString`).\n", + "\n", + "This has the advantage that pieces with substantial internal logic can be implemented in isolation and then assembled into a full program. The challenge then becomes to find how we can decompose our design into such self-contained modules." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Module for consuming a stream" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "dotnet_interactive": { + "language": "fsharp" + }, + "polyglot_notebook": { + "kernelName": "fsharp" + }, + "vscode": { + "languageId": "polyglot-notebook" + } + }, + "outputs": [], + "source": [ + "type StopInsert = {insertWord: string -> unit; stop: unit -> unit}\n", + "\n", + "type ReadString = unit -> Async\n", + "\n", + "let readAnswer (read: ReadString) (si: StopInsert) =\n", + " let rec loop ()=\n", + " task {\n", + " let! r = read()\n", + " match r with\n", + " | Some w -> \n", + " si.insertWord w\n", + " return! loop ()\n", + " | None -> \n", + " si.stop()\n", + " }\n", + " loop() |> Async.AwaitTask |> Async.Start\n", + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Module for defining stream\n", + "\n", + "\n", + "Another indicators of possible decomposition are:\n", + "- we need code to initialize values\n", + "- we have producers and consumers " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "fsharp" + }, + "polyglot_notebook": { + "kernelName": "fsharp" + }, + "vscode": { + "languageId": "polyglot-notebook" + } + }, + "outputs": [], + "source": [ + "#r \"nuget: FSharp.Control.AsyncSeq\"\n", + "\n", + "open FSharp.Control\n", + "\n", + "type Message = AnswerSegment of AsyncReplyChannel\n", + "\n", + "type Provider = MailboxProcessor -> Async\n", + "type GetProvider = unit -> AsyncSeq\n", + "\n", + "let readSegments (inbox: MailboxProcessor) (xs: AsyncSeq) =\n", + " xs\n", + " |> AsyncSeq.takeWhileAsync (fun x ->\n", + " async {\n", + " let! msg = inbox.TryReceive()\n", + "\n", + " return\n", + " match msg with\n", + " | Some (AnswerSegment chan) ->\n", + " chan.Reply x\n", + " true\n", + " | _ -> false\n", + " })\n", + " |> AsyncSeq.toListAsync\n", + " |> Async.Ignore\n", + "\n", + "let stream (g: GetProvider) =\n", + " let mb = MailboxProcessor.Start (fun inbox -> g() |> readSegments inbox)\n", + " fun () -> mb.PostAndTryAsyncReply(AnswerSegment, timeout = 1000)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Definining and consuming stream" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": { + "dotnet_interactive": { + "language": "fsharp" + }, + "polyglot_notebook": { + "kernelName": "fsharp" + }, + "vscode": { + "languageId": "polyglot-notebook" + } + }, + "outputs": [], + "source": [ + "let streamFlow (g: GetProvider) (si: StopInsert) = readAnswer (stream g) si" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Implementing `GetProvider`" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": { + "dotnet_interactive": { + "language": "fsharp" + }, + "polyglot_notebook": { + "kernelName": "fsharp" + }, + "vscode": { + "languageId": "polyglot-notebook" + } + }, + "outputs": [], + "source": [ + "open FSharp.Control\n", + "\n", + "type Model = string\n", + "type Provider = string\n", + "type Prompt = string\n", + "type Key = string\n", + "type KeyEnvVar = string\n", + "\n", + "type Active = {provider: Provider; model: Model}\n", + "type ProviderImpl = {models: Model list; answerer: Model -> Prompt -> AsyncSeq}\n", + "\n", + "type Conf = {active: Active; providers: Map}\n", + "\n", + "type ProviderModule = {implementation: Key -> ProviderImpl; keyVar: KeyEnvVar; provider: Provider}\n", + "\n", + "let getenv s =\n", + " System.Environment.GetEnvironmentVariable s |> Option.ofObj\n", + "\n", + "let initProviders (xs: ProviderModule list) (_default: Provider) =\n", + " let providers = \n", + " xs\n", + " |> List.choose (fun pm ->\n", + " getenv pm.keyVar |> Option.map (fun key -> \n", + " pm.provider, pm.implementation key\n", + " ) \n", + " )\n", + " |> Map.ofList\n", + " let active = {provider = _default; model = providers[_default].models.Head}\n", + " {active = active; providers = providers}\n", + "\n", + "let getProvider (conf: unit -> Conf) (getPrompt: unit -> Prompt) () =\n", + " let c = conf ()\n", + " let prompt = getPrompt ()\n", + " c.providers[c.active.provider].answerer c.active.model prompt\n", + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Implementing `StopInsert`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "fsharp" + }, + "polyglot_notebook": { + "kernelName": "fsharp" + }, + "vscode": { + "languageId": "polyglot-notebook" + } + }, + "outputs": [], + "source": [ + "#r \"nuget:GtkSharp, 3.24.24.95\"\n", + "open Gtk\n", + "\n", + "type AdjustWord =\n", + " { chatDisplay: TextView\n", + " adjustment: Adjustment }\n", + "\n", + "let insertWord (b: Builder) : string -> unit =\n", + " let adj = b.GetObject \"text_adjustment\" :?> Adjustment\n", + " let chatDisplay = b.GetObject \"chat_display\" :?> TextView\n", + " let f w =\n", + " chatDisplay.Buffer.PlaceCursor chatDisplay.Buffer.EndIter\n", + " chatDisplay.Buffer.InsertAtCursor w\n", + " adj.Value <- adj.Upper\n", + "\n", + " f\n", + "\n", + "let newStopInsert (builder: Builder) =\n", + " let answerSpinner = builder.GetObject \"answer_spinner\" :?> Spinner\n", + "\n", + " { stop = answerSpinner.Stop\n", + " insertWord = insertWord builder }" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".NET (C#)", + "language": "C#", + "name": ".net-csharp" + }, + "language_info": { + "name": "python" + }, + "polyglot_notebook": { + "kernelInfo": { + "defaultKernelName": "csharp", + "items": [ + { + "aliases": [], + "name": "csharp" + } + ] + } + } + }, + "nbformat": 4, + "nbformat_minor": 2 +}