Skip to content

Commit 78288e2

Browse files
committed
wip bottom-up
1 parent b24cd29 commit 78288e2

File tree

3 files changed

+288
-6
lines changed

3 files changed

+288
-6
lines changed

README.md

+6-5
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ patterns that traditional imperative programming hides. I was delighted to disco
66
Another surprise is that F# is a great candidate to explore functional data structures, which seem could be
77
a good update to the excellent work by [Chris Okasaki](https://en.wikipedia.org/wiki/Chris_Okasaki#Purely_functional_data_structures)
88

9-
- [Breadth-First Search](./docs/bfs.ipynb)
10-
- [Queue](./docs/queue.ipynb)
11-
- [Pattern matching nesting reduction](./docs/pattern_matching_nesting_reduction.ipynb)
12-
- [Fixed points](./docs/fixed_points.ipynb)
13-
- [Exception vs Result](./docs/exception_vs_result.ipynb)
9+
- [Breadth-First Search](./bfs.ipynb)
10+
- [Queue](./queue.ipynb)
11+
- [Pattern matching nesting reduction](./pattern_matching_nesting_reduction.ipynb)
12+
- [Fixed points](./fixed_points.ipynb)
13+
- [Exception vs Result](./exception_vs_result.ipynb)
14+
- [Bottom-up approach to devise a structure](./r0b0t.ipynb)

docs/index.ipynb

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
"- [Queue](./queue.ipynb)\n",
1717
"- [Pattern matching nesting reduction](./pattern_matching_nesting_reduction.ipynb)\n",
1818
"- [Fixed points](./fixed_points.ipynb)\n",
19-
"- [Exception vs Result](./exception_vs_result.ipynb)"
19+
"- [Exception vs Result](./exception_vs_result.ipynb)\n",
20+
"- [Bottom-up approach to devise a structure](./r0b0t.ipynb)"
2021
]
2122
}
2223
],

docs/r0b0t.ipynb

+280
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"## Streaming data from APIs"
8+
]
9+
},
10+
{
11+
"cell_type": "markdown",
12+
"metadata": {},
13+
"source": [
14+
"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",
15+
"\n",
16+
"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",
17+
"\n",
18+
"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",
19+
"\n",
20+
"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."
21+
]
22+
},
23+
{
24+
"cell_type": "markdown",
25+
"metadata": {},
26+
"source": [
27+
"Module for consuming a stream"
28+
]
29+
},
30+
{
31+
"cell_type": "code",
32+
"execution_count": 20,
33+
"metadata": {
34+
"dotnet_interactive": {
35+
"language": "fsharp"
36+
},
37+
"polyglot_notebook": {
38+
"kernelName": "fsharp"
39+
},
40+
"vscode": {
41+
"languageId": "polyglot-notebook"
42+
}
43+
},
44+
"outputs": [],
45+
"source": [
46+
"type StopInsert = {insertWord: string -> unit; stop: unit -> unit}\n",
47+
"\n",
48+
"type ReadString = unit -> Async<string option>\n",
49+
"\n",
50+
"let readAnswer (read: ReadString) (si: StopInsert) =\n",
51+
" let rec loop ()=\n",
52+
" task {\n",
53+
" let! r = read()\n",
54+
" match r with\n",
55+
" | Some w -> \n",
56+
" si.insertWord w\n",
57+
" return! loop ()\n",
58+
" | None -> \n",
59+
" si.stop()\n",
60+
" }\n",
61+
" loop() |> Async.AwaitTask |> Async.Start\n",
62+
" "
63+
]
64+
},
65+
{
66+
"cell_type": "markdown",
67+
"metadata": {},
68+
"source": [
69+
"Module for defining stream\n",
70+
"\n",
71+
"\n",
72+
"Another indicators of possible decomposition are:\n",
73+
"- we need code to initialize values\n",
74+
"- we have producers and consumers "
75+
]
76+
},
77+
{
78+
"cell_type": "code",
79+
"execution_count": null,
80+
"metadata": {
81+
"dotnet_interactive": {
82+
"language": "fsharp"
83+
},
84+
"polyglot_notebook": {
85+
"kernelName": "fsharp"
86+
},
87+
"vscode": {
88+
"languageId": "polyglot-notebook"
89+
}
90+
},
91+
"outputs": [],
92+
"source": [
93+
"#r \"nuget: FSharp.Control.AsyncSeq\"\n",
94+
"\n",
95+
"open FSharp.Control\n",
96+
"\n",
97+
"type Message = AnswerSegment of AsyncReplyChannel<string>\n",
98+
"\n",
99+
"type Provider = MailboxProcessor<Message> -> Async<unit>\n",
100+
"type GetProvider = unit -> AsyncSeq<string>\n",
101+
"\n",
102+
"let readSegments (inbox: MailboxProcessor<Message>) (xs: AsyncSeq<string>) =\n",
103+
" xs\n",
104+
" |> AsyncSeq.takeWhileAsync (fun x ->\n",
105+
" async {\n",
106+
" let! msg = inbox.TryReceive()\n",
107+
"\n",
108+
" return\n",
109+
" match msg with\n",
110+
" | Some (AnswerSegment chan) ->\n",
111+
" chan.Reply x\n",
112+
" true\n",
113+
" | _ -> false\n",
114+
" })\n",
115+
" |> AsyncSeq.toListAsync\n",
116+
" |> Async.Ignore\n",
117+
"\n",
118+
"let stream (g: GetProvider) =\n",
119+
" let mb = MailboxProcessor.Start (fun inbox -> g() |> readSegments inbox)\n",
120+
" fun () -> mb.PostAndTryAsyncReply(AnswerSegment, timeout = 1000)"
121+
]
122+
},
123+
{
124+
"cell_type": "markdown",
125+
"metadata": {},
126+
"source": [
127+
"Definining and consuming stream"
128+
]
129+
},
130+
{
131+
"cell_type": "code",
132+
"execution_count": 22,
133+
"metadata": {
134+
"dotnet_interactive": {
135+
"language": "fsharp"
136+
},
137+
"polyglot_notebook": {
138+
"kernelName": "fsharp"
139+
},
140+
"vscode": {
141+
"languageId": "polyglot-notebook"
142+
}
143+
},
144+
"outputs": [],
145+
"source": [
146+
"let streamFlow (g: GetProvider) (si: StopInsert) = readAnswer (stream g) si"
147+
]
148+
},
149+
{
150+
"cell_type": "markdown",
151+
"metadata": {},
152+
"source": [
153+
"## Implementing `GetProvider`"
154+
]
155+
},
156+
{
157+
"cell_type": "code",
158+
"execution_count": 23,
159+
"metadata": {
160+
"dotnet_interactive": {
161+
"language": "fsharp"
162+
},
163+
"polyglot_notebook": {
164+
"kernelName": "fsharp"
165+
},
166+
"vscode": {
167+
"languageId": "polyglot-notebook"
168+
}
169+
},
170+
"outputs": [],
171+
"source": [
172+
"open FSharp.Control\n",
173+
"\n",
174+
"type Model = string\n",
175+
"type Provider = string\n",
176+
"type Prompt = string\n",
177+
"type Key = string\n",
178+
"type KeyEnvVar = string\n",
179+
"\n",
180+
"type Active = {provider: Provider; model: Model}\n",
181+
"type ProviderImpl = {models: Model list; answerer: Model -> Prompt -> AsyncSeq<string>}\n",
182+
"\n",
183+
"type Conf = {active: Active; providers: Map<Provider, ProviderImpl>}\n",
184+
"\n",
185+
"type ProviderModule = {implementation: Key -> ProviderImpl; keyVar: KeyEnvVar; provider: Provider}\n",
186+
"\n",
187+
"let getenv s =\n",
188+
" System.Environment.GetEnvironmentVariable s |> Option.ofObj\n",
189+
"\n",
190+
"let initProviders (xs: ProviderModule list) (_default: Provider) =\n",
191+
" let providers = \n",
192+
" xs\n",
193+
" |> List.choose (fun pm ->\n",
194+
" getenv pm.keyVar |> Option.map (fun key -> \n",
195+
" pm.provider, pm.implementation key\n",
196+
" ) \n",
197+
" )\n",
198+
" |> Map.ofList\n",
199+
" let active = {provider = _default; model = providers[_default].models.Head}\n",
200+
" {active = active; providers = providers}\n",
201+
"\n",
202+
"let getProvider (conf: unit -> Conf) (getPrompt: unit -> Prompt) () =\n",
203+
" let c = conf ()\n",
204+
" let prompt = getPrompt ()\n",
205+
" c.providers[c.active.provider].answerer c.active.model prompt\n",
206+
" "
207+
]
208+
},
209+
{
210+
"cell_type": "markdown",
211+
"metadata": {},
212+
"source": [
213+
"## Implementing `StopInsert`"
214+
]
215+
},
216+
{
217+
"cell_type": "code",
218+
"execution_count": null,
219+
"metadata": {
220+
"dotnet_interactive": {
221+
"language": "fsharp"
222+
},
223+
"polyglot_notebook": {
224+
"kernelName": "fsharp"
225+
},
226+
"vscode": {
227+
"languageId": "polyglot-notebook"
228+
}
229+
},
230+
"outputs": [],
231+
"source": [
232+
"#r \"nuget:GtkSharp, 3.24.24.95\"\n",
233+
"open Gtk\n",
234+
"\n",
235+
"type AdjustWord =\n",
236+
" { chatDisplay: TextView\n",
237+
" adjustment: Adjustment }\n",
238+
"\n",
239+
"let insertWord (b: Builder) : string -> unit =\n",
240+
" let adj = b.GetObject \"text_adjustment\" :?> Adjustment\n",
241+
" let chatDisplay = b.GetObject \"chat_display\" :?> TextView\n",
242+
" let f w =\n",
243+
" chatDisplay.Buffer.PlaceCursor chatDisplay.Buffer.EndIter\n",
244+
" chatDisplay.Buffer.InsertAtCursor w\n",
245+
" adj.Value <- adj.Upper\n",
246+
"\n",
247+
" f\n",
248+
"\n",
249+
"let newStopInsert (builder: Builder) =\n",
250+
" let answerSpinner = builder.GetObject \"answer_spinner\" :?> Spinner\n",
251+
"\n",
252+
" { stop = answerSpinner.Stop\n",
253+
" insertWord = insertWord builder }"
254+
]
255+
}
256+
],
257+
"metadata": {
258+
"kernelspec": {
259+
"display_name": ".NET (C#)",
260+
"language": "C#",
261+
"name": ".net-csharp"
262+
},
263+
"language_info": {
264+
"name": "python"
265+
},
266+
"polyglot_notebook": {
267+
"kernelInfo": {
268+
"defaultKernelName": "csharp",
269+
"items": [
270+
{
271+
"aliases": [],
272+
"name": "csharp"
273+
}
274+
]
275+
}
276+
}
277+
},
278+
"nbformat": 4,
279+
"nbformat_minor": 2
280+
}

0 commit comments

Comments
 (0)