Skip to content

Commit

Permalink
wip bottom-up
Browse files Browse the repository at this point in the history
  • Loading branch information
lamg committed May 21, 2024
1 parent b24cd29 commit 78288e2
Show file tree
Hide file tree
Showing 3 changed files with 288 additions and 6 deletions.
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
- [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)
3 changes: 2 additions & 1 deletion docs/index.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -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)"
]
}
],
Expand Down
280 changes: 280 additions & 0 deletions docs/r0b0t.ipynb
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
}

0 comments on commit 78288e2

Please sign in to comment.