Skip to content

Commit a6d7ee9

Browse files
authored
Merge pull request #358 from fsprojects/repo-assist/docs-new-10-functions-20260319-d7b1ea2b80d1e1fe
[Repo Assist] docs: add documentation for new 1.0.0 functions
2 parents 5a41331 + 5333cbe commit a6d7ee9

File tree

4 files changed

+202
-13
lines changed

4 files changed

+202
-13
lines changed

docs/TaskSeqAdvanced.fsx

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ title: Advanced Task Sequence Operations
44
category: Documentation
55
categoryindex: 2
66
index: 6
7-
description: Advanced F# task sequence operations including groupBy, mapFold, distinct, partition, countBy, compareWith, withCancellation and in-place editing.
8-
keywords: F#, task sequences, TaskSeq, IAsyncEnumerable, groupBy, mapFold, distinct, distinctUntilChanged, except, partition, countBy, compareWith, withCancellation, insertAt, removeAt, updateAt
7+
description: Advanced F# task sequence operations including groupBy, mapFold, threadState, distinct, partition, countBy, compareWith, withCancellation and in-place editing.
8+
keywords: F#, task sequences, TaskSeq, IAsyncEnumerable, groupBy, mapFold, threadState, distinct, distinctUntilChanged, except, partition, countBy, compareWith, withCancellation, insertAt, removeAt, updateAt
99
---
1010
*)
1111
(*** condition: prepare ***)
@@ -26,7 +26,7 @@ keywords: F#, task sequences, TaskSeq, IAsyncEnumerable, groupBy, mapFold, disti
2626
# Advanced Task Sequence Operations
2727
2828
This page covers advanced `TaskSeq<'T>` operations: grouping, stateful transformation with
29-
`mapFold`, deduplication, set-difference, partitioning, counting by key, lexicographic
29+
`mapFold` and `threadState`, deduplication, set-difference, partitioning, counting by key, lexicographic
3030
comparison, cancellation, and positional editing.
3131
3232
*)
@@ -113,15 +113,52 @@ let numbered : Task<string[] * int> =
113113
114114
---
115115
116-
## scan and scanAsync
116+
## threadState and threadStateAsync
117117
118-
`TaskSeq.scan` is the streaming sibling of `fold`: it emits each intermediate state as a new
119-
element, starting with the initial state:
118+
`TaskSeq.threadState` is the lazy, streaming counterpart to `mapFold`. It threads a state
119+
accumulator through the sequence while yielding each mapped result — but unlike `mapFold` it
120+
never materialises the results into an array, and it discards the final state. This makes it
121+
suitable for infinite sequences and pipelines where intermediate results should be streamed rather
122+
than buffered:
120123
121124
*)
122125

123126
let numbers : TaskSeq<int> = TaskSeq.ofSeq (seq { 1..5 })
124127

128+
// Produce a running total without collecting the whole sequence first
129+
let runningSum : TaskSeq<int> =
130+
numbers
131+
|> TaskSeq.threadState (fun acc x -> acc + x, acc + x) 0
132+
133+
// yields lazily: 1, 3, 6, 10, 15
134+
135+
(**
136+
137+
Compare with `scan`, which also emits a running result but prepends the initial state:
138+
139+
```fsharp
140+
let viaScan = numbers |> TaskSeq.scan (fun acc x -> acc + x) 0
141+
// yields: 0, 1, 3, 6, 10, 15 (one extra initial element)
142+
143+
let viaThreadState = numbers |> TaskSeq.threadState (fun acc x -> acc + x, acc + x) 0
144+
// yields: 1, 3, 6, 10, 15 (no initial element; result == new state here)
145+
```
146+
147+
`TaskSeq.threadStateAsync` accepts an asynchronous folder:
148+
149+
*)
150+
151+
let asyncRunningSum : TaskSeq<int> =
152+
numbers
153+
|> TaskSeq.threadStateAsync (fun acc x -> Task.fromResult (acc + x, acc + x)) 0
154+
155+
(**
156+
157+
`TaskSeq.scan` is the streaming sibling of `fold`: it emits each intermediate state as a new
158+
element, starting with the initial state:
159+
160+
*)
161+
125162
let runningTotals : TaskSeq<int> =
126163
numbers |> TaskSeq.scan (fun acc n -> acc + n) 0
127164

docs/TaskSeqCombining.fsx

Lines changed: 98 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ title: Combining Task Sequences
44
category: Documentation
55
categoryindex: 2
66
index: 5
7-
description: How to combine, slice and reshape F# task sequences using append, zip, take, skip, chunkBySize, windowed and related combinators.
8-
keywords: F#, task sequences, TaskSeq, IAsyncEnumerable, append, zip, zip3, take, skip, drop, truncate, takeWhile, skipWhile, chunkBySize, windowed, pairwise, concat
7+
description: How to combine, slice and reshape F# task sequences using append, zip, zipWith, splitAt, take, skip, chunkBySize, chunkBy, windowed and related combinators.
8+
keywords: F#, task sequences, TaskSeq, IAsyncEnumerable, append, zip, zip3, zipWith, zipWith3, splitAt, take, skip, drop, truncate, takeWhile, skipWhile, chunkBySize, chunkBy, windowed, pairwise, concat
99
---
1010
*)
1111
(*** condition: prepare ***)
@@ -26,7 +26,7 @@ keywords: F#, task sequences, TaskSeq, IAsyncEnumerable, append, zip, zip3, take
2626
# Combining Task Sequences
2727
2828
This page covers operations that combine multiple sequences or reshape a single sequence: append,
29-
zip, concat, slicing with take/skip, chunking and windowing.
29+
zip, zipWith, concat, slicing with take/skip/splitAt, chunking and windowing.
3030
3131
*)
3232

@@ -121,6 +121,54 @@ let triples : TaskSeq<char * int * bool> = TaskSeq.zip3 letters nums booleans
121121
122122
---
123123
124+
## zipWith and zipWithAsync
125+
126+
`TaskSeq.zipWith` is like `zip` but applies a mapping function to produce a result instead of
127+
yielding a tuple. The result sequence stops when the shorter source ends:
128+
129+
*)
130+
131+
let addPairs : TaskSeq<int> = TaskSeq.zipWith (+) nums nums
132+
// 2, 4, 6, 8
133+
134+
(**
135+
136+
`TaskSeq.zipWithAsync` accepts an asynchronous mapping function:
137+
138+
*)
139+
140+
let asyncProduct : TaskSeq<int> =
141+
TaskSeq.zipWithAsync (fun a b -> Task.fromResult (a * b)) nums nums
142+
// 1, 4, 9, 16, ...
143+
144+
(**
145+
146+
---
147+
148+
## zipWith3 and zipWithAsync3
149+
150+
`TaskSeq.zipWith3` combines three sequences with a three-argument mapping function, stopping at
151+
the shortest:
152+
153+
*)
154+
155+
let sumThree : TaskSeq<int> =
156+
TaskSeq.zipWith3 (fun a b c -> a + b + c) nums nums nums
157+
// 3, 6, 9, 12, ...
158+
159+
(**
160+
161+
`TaskSeq.zipWithAsync3` takes an asynchronous three-argument mapper:
162+
163+
*)
164+
165+
let asyncSumThree : TaskSeq<int> =
166+
TaskSeq.zipWithAsync3 (fun a b c -> Task.fromResult (a + b + c)) nums nums nums
167+
168+
(**
169+
170+
---
171+
124172
## pairwise
125173
126174
`TaskSeq.pairwise` produces a sequence of consecutive pairs. An input with fewer than two elements
@@ -158,6 +206,26 @@ let atMost10 : TaskSeq<int> = consecutive |> TaskSeq.truncate 10 // 1, 2, 3, 4,
158206
159207
---
160208
209+
## splitAt
210+
211+
`TaskSeq.splitAt count` splits a sequence into a prefix array and a lazy remainder sequence. The
212+
prefix always contains _at most_ `count` elements — it never throws when the sequence is shorter.
213+
The remainder sequence is a lazy view over the unconsumed tail and can be iterated once:
214+
215+
*)
216+
217+
let splitData : TaskSeq<int> = TaskSeq.ofList [ 1..10 ]
218+
219+
let splitExample : Task<int[] * TaskSeq<int>> = TaskSeq.splitAt 4 splitData
220+
// prefix = [|1;2;3;4|], rest = lazy 5,6,7,8,9,10
221+
222+
(**
223+
224+
Unlike `take`/`skip`, a single `splitAt` call evaluates elements only once — the prefix is
225+
materialised eagerly and the rest is yielded lazily without re-reading the source.
226+
227+
---
228+
161229
## skip and drop
162230
163231
`TaskSeq.skip count` skips exactly `count` elements and throws if the source is shorter:
@@ -245,6 +313,33 @@ let chunks : TaskSeq<int[]> = consecutive |> TaskSeq.chunkBySize 2
245313
246314
---
247315
316+
## chunkBy and chunkByAsync
317+
318+
`TaskSeq.chunkBy projection` groups _consecutive_ elements with the same key into `(key, elements[])` pairs.
319+
A new group starts each time the key changes. Unlike `groupBy`, elements that are not adjacent are
320+
**not** merged, so the source order is preserved and the sequence can be infinite:
321+
322+
*)
323+
324+
let words : TaskSeq<string> = TaskSeq.ofList [ "apple"; "apricot"; "banana"; "blueberry"; "cherry" ]
325+
326+
let byFirstLetter : TaskSeq<char * string[]> =
327+
words |> TaskSeq.chunkBy (fun w -> w[0])
328+
// ('a', [|"apple";"apricot"|]), ('b', [|"banana";"blueberry"|]), ('c', [|"cherry"|])
329+
330+
(**
331+
332+
`TaskSeq.chunkByAsync` accepts an asynchronous projection:
333+
334+
*)
335+
336+
let byFirstLetterAsync : TaskSeq<char * string[]> =
337+
words |> TaskSeq.chunkByAsync (fun w -> Task.fromResult w[0])
338+
339+
(**
340+
341+
---
342+
248343
## windowed
249344
250345
`TaskSeq.windowed windowSize` produces a sliding window of exactly `windowSize` consecutive

docs/TaskSeqConsuming.fsx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ category: Documentation
55
categoryindex: 2
66
index: 4
77
description: How to consume F# task sequences using iteration, folding, searching, collecting and aggregation operations.
8-
keywords: F#, task sequences, TaskSeq, IAsyncEnumerable, iter, fold, find, toArray, toList, sum, average, length, head, last, contains, exists, forall
8+
keywords: F#, task sequences, TaskSeq, IAsyncEnumerable, iter, fold, find, toArray, toList, sum, average, length, head, last, firstOrDefault, lastOrDefault, contains, exists, forall
99
---
1010
*)
1111
(*** condition: prepare ***)
@@ -220,7 +220,7 @@ let countEvens : Task<int> = numbers |> TaskSeq.lengthBy (fun n -> n % 2 = 0)
220220
221221
---
222222
223-
## Element access: head, last, item, exactlyOne
223+
## Element access: head, last, item, exactlyOne, firstOrDefault, lastOrDefault
224224
225225
*)
226226

@@ -237,6 +237,20 @@ let tryOnly : Task<int option> = TaskSeq.singleton 42 |> TaskSeq.tryExactlyOne
237237

238238
(**
239239
240+
`TaskSeq.firstOrDefault` and `TaskSeq.lastOrDefault` return a caller-supplied default when the
241+
sequence is empty — useful as a concise alternative to `tryHead`/`tryLast` when `None` would
242+
need to be immediately unwrapped:
243+
244+
*)
245+
246+
let firstOrZero : Task<int> = TaskSeq.empty<int> |> TaskSeq.firstOrDefault 0 // 0
247+
let lastOrZero : Task<int> = TaskSeq.empty<int> |> TaskSeq.lastOrDefault 0 // 0
248+
249+
let firstOrMinus1 : Task<int> = numbers |> TaskSeq.firstOrDefault -1 // 1
250+
let lastOrMinus1 : Task<int> = numbers |> TaskSeq.lastOrDefault -1 // 5
251+
252+
(**
253+
240254
---
241255
242256
## Searching: find, pick, contains, exists, forall

docs/TaskSeqGenerating.fsx

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ category: Documentation
55
categoryindex: 2
66
index: 2
77
description: How to create F# task sequences using the taskSeq computation expression, init, unfold, and conversion functions.
8-
keywords: F#, task sequences, TaskSeq, IAsyncEnumerable, taskSeq, computation expression, init, unfold, ofArray, ofSeq, singleton, replicate
8+
keywords: F#, task sequences, TaskSeq, IAsyncEnumerable, taskSeq, computation expression, init, unfold, ofArray, ofSeq, singleton, replicate, replicateInfinite, replicateUntilNoneAsync
99
---
1010
*)
1111
(*** condition: prepare ***)
@@ -237,7 +237,7 @@ let countingAsync : TaskSeq<int> =
237237
238238
---
239239
240-
## singleton, replicate and empty
240+
## singleton, replicate, replicateInfinite, and empty
241241
242242
*)
243243

@@ -249,6 +249,49 @@ let nothing : TaskSeq<int> = TaskSeq.empty<int>
249249

250250
(**
251251
252+
`TaskSeq.replicateInfinite` yields a constant value indefinitely. Always combine it with a
253+
bounding operation such as `take` or `takeWhile`:
254+
255+
*)
256+
257+
let infinitePings : TaskSeq<string> = TaskSeq.replicateInfinite "ping"
258+
259+
let first10pings : TaskSeq<string> = infinitePings |> TaskSeq.take 10
260+
261+
(**
262+
263+
`TaskSeq.replicateInfiniteAsync` calls a function on every step, useful for polling or streaming
264+
side-effectful sources:
265+
266+
*)
267+
268+
let mutable counter = 0
269+
270+
let pollingSeq : TaskSeq<int> =
271+
TaskSeq.replicateInfiniteAsync (fun () ->
272+
task {
273+
counter <- counter + 1
274+
return counter
275+
})
276+
277+
let first5counts : TaskSeq<int> = pollingSeq |> TaskSeq.take 5
278+
279+
(**
280+
281+
`TaskSeq.replicateUntilNoneAsync` stops when the function returns `None`, making it easy to
282+
wrap a pull-based source that signals end-of-stream with `None`:
283+
284+
*)
285+
286+
let readLine (reader: System.IO.TextReader) =
287+
TaskSeq.replicateUntilNoneAsync (fun () ->
288+
task {
289+
let! line = reader.ReadLineAsync()
290+
return if line = null then None else Some line
291+
})
292+
293+
(**
294+
252295
---
253296
254297
## delay

0 commit comments

Comments
 (0)