@@ -4,8 +4,8 @@ title: Combining Task Sequences
44category: Documentation
55categoryindex: 2
66index: 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
2828This 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
0 commit comments