-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
288 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<Message>`, which is used in `readAnswer` just an `unit -> Async<string option>` (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<string option>\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<string>\n", | ||
"\n", | ||
"type Provider = MailboxProcessor<Message> -> Async<unit>\n", | ||
"type GetProvider = unit -> AsyncSeq<string>\n", | ||
"\n", | ||
"let readSegments (inbox: MailboxProcessor<Message>) (xs: AsyncSeq<string>) =\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<string>}\n", | ||
"\n", | ||
"type Conf = {active: Active; providers: Map<Provider, ProviderImpl>}\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 | ||
} |