-
Notifications
You must be signed in to change notification settings - Fork 12
Expand file tree
/
Copy pathTaskSeqGenerating.fsx
More file actions
339 lines (233 loc) · 7.33 KB
/
TaskSeqGenerating.fsx
File metadata and controls
339 lines (233 loc) · 7.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
(**
---
title: Generating Task Sequences
category: Documentation
categoryindex: 2
index: 2
description: How to create F# task sequences using the taskSeq computation expression, init, unfold, and conversion functions.
keywords: F#, task sequences, TaskSeq, IAsyncEnumerable, taskSeq, computation expression, init, unfold, ofArray, ofSeq, singleton, replicate, replicateInfinite, replicateUntilNoneAsync
---
*)
(*** condition: prepare ***)
#nowarn "211"
#I "../src/FSharp.Control.TaskSeq/bin/Release/netstandard2.1"
#r "FSharp.Control.TaskSeq.dll"
(*** condition: fsx ***)
#if FSX
#r "nuget: FSharp.Control.TaskSeq,{{fsdocs-package-version}}"
#endif // FSX
(*** condition: ipynb ***)
#if IPYNB
#r "nuget: FSharp.Control.TaskSeq,{{fsdocs-package-version}}"
#endif // IPYNB
(**
# Generating Task Sequences
This page covers the main ways to create `TaskSeq<'T>` values: the `taskSeq` computation
expression, factory functions such as `TaskSeq.init` and `TaskSeq.unfold`, and conversion
functions that wrap existing collections.
*)
open System.Threading.Tasks
open FSharp.Control
(**
## Computation Expression Syntax
`taskSeq { ... }` is a computation expression that lets you write asynchronous sequences
using familiar F# constructs. Under the hood it compiles to a resumable state machine, so
there is no allocation per element.
### Yielding values
Use `yield` to emit a single value and `yield!` to splice in another sequence:
*)
let helloWorld = taskSeq {
yield "hello"
yield "world"
}
let combined = taskSeq {
yield! helloWorld
yield "!"
}
(**
### Conditionals
`if`/`then`/`else` works just like in ordinary F#:
*)
let evenNumbers = taskSeq {
for i in 1..10 do
if i % 2 = 0 then
yield i
}
(**
### Match expressions
`match` expressions are fully supported:
*)
type Shape =
| Circle of radius: float
| Rectangle of width: float * height: float
let areas = taskSeq {
for shape in [ Circle 3.0; Rectangle(4.0, 5.0); Circle 1.5 ] do
match shape with
| Circle r -> yield System.Math.PI * r * r
| Rectangle(w, h) -> yield w * h
}
(**
### For loops
`for` iterates over any `seq<'T>`/`IEnumerable<'T>` synchronously, or over another
`TaskSeq<'T>` asynchronously:
*)
let squaresOfList = taskSeq {
for n in [ 1; 2; 3; 4; 5 ] do
yield n * n
}
// Iterate another TaskSeq
let doubled = taskSeq {
for n in squaresOfList do
yield n * 2
}
(**
### While loops
`while` emits elements until a condition becomes false. Async operations can appear in the
loop body:
*)
let countdown = taskSeq {
let mutable i = 5
while i > 0 do
yield i
do! Task.Delay 100
i <- i - 1
}
(**
### Awaiting tasks with `let!` and `do!`
Inside `taskSeq { ... }` you can await any `Task<'T>` with `let!` and any `Task<unit>` with
`do!`:
*)
let fetchData (url: string) : Task<string> =
task { return $"data from {url}" } // placeholder
let results = taskSeq {
for url in [ "https://example.com/a"; "https://example.com/b" ] do
let! data = fetchData url
yield data
}
(**
### Use bindings and try / with
`use` and `use!` dispose the resource when the sequence finishes or is abandoned. `try/with`
and `try/finally` work as expected:
*)
let withResource = taskSeq {
use resource = { new System.IDisposable with member _.Dispose() = () } // placeholder
yield 1
yield 2
}
let withErrorHandling = taskSeq {
try
yield 1
failwith "oops"
yield 2
with ex ->
yield -1
}
(**
---
## init and initInfinite
`TaskSeq.init count initializer` generates `count` elements by calling `initializer` with the
zero-based index:
*)
// [| 0; 1; 4; 9; 16 |]
let squares : TaskSeq<int> = TaskSeq.init 5 (fun i -> i * i)
// With an async initializer
let asyncSquares : TaskSeq<int> =
TaskSeq.initAsync 5 (fun i -> task { return i * i })
(**
`TaskSeq.initInfinite` generates an unbounded sequence — use `TaskSeq.take` or
`TaskSeq.takeWhile` to limit consumption:
*)
let naturals : TaskSeq<int> = TaskSeq.initInfinite id
let first10 : TaskSeq<int> = naturals |> TaskSeq.take 10 // 0 .. 9
(**
---
## unfold
`TaskSeq.unfold` derives a sequence from a state value. Each call to the generator returns
either `None` (end) or `Some (element, nextState)`:
*)
// 0, 1, 2, ... up to but not including 5
let counting : TaskSeq<int> =
TaskSeq.unfold
(fun state ->
if state < 5 then
Some(state, state + 1)
else
None)
0
// Same with an async generator
let countingAsync : TaskSeq<int> =
TaskSeq.unfoldAsync
(fun state ->
task {
if state < 5 then
return Some(state, state + 1)
else
return None
})
0
(**
---
## singleton, replicate, replicateInfinite, and empty
*)
let one : TaskSeq<string> = TaskSeq.singleton "hello"
let fives : TaskSeq<int> = TaskSeq.replicate 3 5 // 5, 5, 5
let nothing : TaskSeq<int> = TaskSeq.empty<int>
(**
`TaskSeq.replicateInfinite` yields a constant value indefinitely. Always combine it with a
bounding operation such as `take` or `takeWhile`:
*)
let infinitePings : TaskSeq<string> = TaskSeq.replicateInfinite "ping"
let first10pings : TaskSeq<string> = infinitePings |> TaskSeq.take 10
(**
`TaskSeq.replicateInfiniteAsync` calls a function on every step, useful for polling or streaming
side-effectful sources:
*)
let mutable counter = 0
let pollingSeq : TaskSeq<int> =
TaskSeq.replicateInfiniteAsync (fun () ->
task {
counter <- counter + 1
return counter
})
let first5counts : TaskSeq<int> = pollingSeq |> TaskSeq.take 5
(**
`TaskSeq.replicateUntilNoneAsync` stops when the function returns `None`, making it easy to
wrap a pull-based source that signals end-of-stream with `None`:
*)
let readLine (reader: System.IO.TextReader) =
TaskSeq.replicateUntilNoneAsync (fun () ->
task {
let! line = reader.ReadLineAsync()
return if line = null then None else Some line
})
(**
---
## delay
`TaskSeq.delay` creates a sequence whose body is not evaluated until iteration starts. This
is useful when the sequence depends on side-effectful initialisation:
*)
let deferred =
TaskSeq.delay (fun () ->
taskSeq {
printfn "sequence started"
yield! [ 1; 2; 3 ]
})
(**
---
## Converting from existing collections
All of these produce a `TaskSeq<'T>` that replays the source on each iteration:
*)
let fromArray : TaskSeq<int> = TaskSeq.ofArray [| 1; 2; 3 |]
let fromList : TaskSeq<int> = TaskSeq.ofList [ 1; 2; 3 ]
let fromSeq : TaskSeq<int> = TaskSeq.ofSeq (seq { 1..3 })
let fromResizeArray : TaskSeq<int> = TaskSeq.ofResizeArray (System.Collections.Generic.List<int>([ 1; 2; 3 ]))
(**
You can also wrap existing task or async collections. Note that wrapping a list of already
started `Task`s does **not** guarantee sequential execution—the tasks are already running:
*)
// Sequence of not-yet-started task factories (safe)
let fromTaskSeq : TaskSeq<int> =
TaskSeq.ofTaskSeq (seq { yield task { return 1 }; yield task { return 2 } })
// Sequence of asyncs (each started on demand as the sequence is consumed)
let fromAsyncSeq : TaskSeq<int> =
TaskSeq.ofAsyncSeq (seq { yield async { return 1 }; yield async { return 2 } })