Header menu logo FSharp.Control.TaskSeq

Combining Task Sequences

This page covers operations that combine multiple sequences or reshape a single sequence: append, zip, concat, slicing with take/skip, chunking and windowing.

open FSharp.Control

Append

TaskSeq.append produces all elements of the first sequence followed by all elements of the second. The second sequence does not start until the first is exhausted:

let first = TaskSeq.ofList [ 1; 2; 3 ]
let second = TaskSeq.ofList [ 4; 5; 6 ]

let appended : TaskSeq<int> = TaskSeq.append first second // 1, 2, 3, 4, 5, 6

Inside taskSeq { ... }, yield! is the natural way to concatenate:

let combined = taskSeq {
    yield! first
    yield! second
}

TaskSeq.appendSeq appends a plain seq<'T> after a task sequence. TaskSeq.prependSeq prepends a plain seq<'T> before a task sequence:

let withPrefix : TaskSeq<int> = TaskSeq.prependSeq [ 0 ] first // 0, 1, 2, 3
let withSuffix : TaskSeq<int> = TaskSeq.appendSeq first [ 4; 5 ] // 1, 2, 3, 4, 5

concat

TaskSeq.concat flattens a task sequence of task sequences into a single flat sequence. Each inner sequence is consumed fully before the next one begins:

let nested : TaskSeq<TaskSeq<int>> =
    TaskSeq.ofList
        [ TaskSeq.ofList [ 1; 2 ]
          TaskSeq.ofList [ 3; 4 ]
          TaskSeq.ofList [ 5; 6 ] ]

let flat : TaskSeq<int> = TaskSeq.concat nested // 1, 2, 3, 4, 5, 6

Overloads also exist for TaskSeq<seq<'T>>, TaskSeq<'T list>, TaskSeq<'T[]>, and TaskSeq<ResizeArray<'T>>.


zip and zip3

TaskSeq.zip pairs up elements from two sequences, stopping when the shorter sequence ends:

let letters : TaskSeq<char> = TaskSeq.ofList [ 'a'; 'b'; 'c' ]
let nums : TaskSeq<int> = TaskSeq.ofList [ 1; 2; 3; 4 ]

let pairs : TaskSeq<char * int> = TaskSeq.zip letters nums
// ('a',1), ('b',2), ('c',3)  — stops when letters runs out

TaskSeq.zip3 does the same for three sequences:

let booleans : TaskSeq<bool> = TaskSeq.ofList [ true; false; true ]

let triples : TaskSeq<char * int * bool> = TaskSeq.zip3 letters nums booleans

pairwise

TaskSeq.pairwise produces a sequence of consecutive pairs. An input with fewer than two elements produces an empty result:

let consecutive : TaskSeq<int> = TaskSeq.ofList [ 1; 2; 3; 4; 5 ]

let pairs2 : TaskSeq<int * int> = consecutive |> TaskSeq.pairwise
// (1,2), (2,3), (3,4), (4,5)

take and truncate

TaskSeq.take count yields exactly count elements and throws if the source is shorter:

let first3 : TaskSeq<int> = consecutive |> TaskSeq.take 3 // 1, 2, 3

TaskSeq.truncate count yields at most count elements without throwing when the source is shorter:

let atMost10 : TaskSeq<int> = consecutive |> TaskSeq.truncate 10 // 1, 2, 3, 4, 5

skip and drop

TaskSeq.skip count skips exactly count elements and throws if the source is shorter:

let afterFirst2 : TaskSeq<int> = consecutive |> TaskSeq.skip 2 // 3, 4, 5

TaskSeq.drop count drops at most count elements without throwing:

let safeAfter10 : TaskSeq<int> = consecutive |> TaskSeq.drop 10 // empty

takeWhile and takeWhileInclusive

TaskSeq.takeWhile predicate yields elements while the predicate is true, then stops (the element that caused the stop is not yielded):

let lessThan4 : TaskSeq<int> = consecutive |> TaskSeq.takeWhile (fun n -> n < 4)
// 1, 2, 3

TaskSeq.takeWhileInclusive yields the first element for which the predicate is false and then stops — so at least one element is always yielded from a non-empty source:

let upToFirstGe4 : TaskSeq<int> =
    consecutive |> TaskSeq.takeWhileInclusive (fun n -> n < 4)
// 1, 2, 3, 4

Async variants: TaskSeq.takeWhileAsync, TaskSeq.takeWhileInclusiveAsync.


skipWhile and skipWhileInclusive

TaskSeq.skipWhile predicate skips elements while the predicate is true, then yields the rest (the first failing element is yielded):

let from3 : TaskSeq<int> = consecutive |> TaskSeq.skipWhile (fun n -> n < 3)
// 3, 4, 5

TaskSeq.skipWhileInclusive also skips the first element for which the predicate is false:

let afterFirst3 : TaskSeq<int> =
    consecutive |> TaskSeq.skipWhileInclusive (fun n -> n < 3)
// 4, 5

Async variants: TaskSeq.skipWhileAsync, TaskSeq.skipWhileInclusiveAsync.


chunkBySize

TaskSeq.chunkBySize chunkSize divides the sequence into non-overlapping arrays of at most chunkSize elements. The last chunk may be smaller if the sequence does not divide evenly:

let chunks : TaskSeq<int[]> = consecutive |> TaskSeq.chunkBySize 2
// [|1;2|], [|3;4|], [|5|]

windowed

TaskSeq.windowed windowSize produces a sliding window of exactly windowSize consecutive elements. The result is empty if the source has fewer elements than the window size:

let windows : TaskSeq<int[]> = consecutive |> TaskSeq.windowed 3
// [|1;2;3|], [|2;3;4|], [|3;4;5|]

windowed uses a ring buffer internally, so each window allocation is separate — safe to store the windows independently.

Multiple items
namespace FSharp

--------------------
namespace Microsoft.FSharp
Multiple items
namespace FSharp.Control

--------------------
namespace Microsoft.FSharp.Control
val first: TaskSeq<int>
Multiple items
module TaskSeq from FSharp.Control.TaskSeqExtensions

--------------------
type TaskSeq = static member append: source1: TaskSeq<'T> -> source2: TaskSeq<'T> -> TaskSeq<'T> static member appendSeq: source1: TaskSeq<'T> -> source2: 'T seq -> TaskSeq<'T> static member box: source: TaskSeq<'T> -> TaskSeq<obj> static member cast: source: TaskSeq<obj> -> TaskSeq<'U> static member choose: chooser: ('T -> 'U option) -> source: TaskSeq<'T> -> TaskSeq<'U> static member chooseAsync: chooser: ('T -> #Task<'U option>) -> source: TaskSeq<'T> -> TaskSeq<'U> static member chunkBySize: chunkSize: int -> source: TaskSeq<'T> -> TaskSeq<'T array> static member collect: binder: ('T -> #TaskSeq<'U>) -> source: TaskSeq<'T> -> TaskSeq<'U> static member collectAsync: binder: ('T -> #Task<'TSeqU>) -> source: TaskSeq<'T> -> TaskSeq<'U> (requires 'TSeqU :> TaskSeq<'U>) static member collectSeq: binder: ('T -> #('U seq)) -> source: TaskSeq<'T> -> TaskSeq<'U> ...

--------------------
type TaskSeq<'T> = System.Collections.Generic.IAsyncEnumerable<'T>
<summary> Represents a task sequence and is the output of using the <paramref name="taskSeq{...}" /> computation expression from this library. It is an alias for <see cref="T:System.IAsyncEnumerable&lt;_&gt;" />. </summary>

--------------------
type TaskSeq<'Machine,'T (requires 'Machine :> IAsyncStateMachine and 'Machine :> IResumableStateMachine<TaskSeqStateMachineData<'T>>)> = inherit TaskSeqBase<'T> interface IValueTaskSource interface IValueTaskSource<bool> interface IAsyncStateMachine interface IAsyncEnumerable<'T> interface IAsyncEnumerator<'T> new: unit -> TaskSeq<'Machine,'T> member InitMachineData: ct: CancellationToken * machine: byref<'Machine> -> unit override MoveNextAsyncResult: unit -> ValueTask<bool>
<summary> Main implementation of generic <see cref="T:System.IAsyncEnumerable&lt;'T&gt;" /> and related interfaces, which forms the meat of the logic behind <see cref="taskSeq" /> computation expresssions. For use by this library only, should not be used directly in user code. Its operation depends highly on resumable state. </summary>

--------------------
new: unit -> TaskSeq<'Machine,'T>
static member TaskSeq.ofList: source: 'T list -> TaskSeq<'T>
val second: TaskSeq<int>
val appended: TaskSeq<int>
Multiple items
val int: value: 'T -> int (requires member op_Explicit)

--------------------
type int = int32

--------------------
type int<'Measure> = int
static member TaskSeq.append: source1: TaskSeq<'T> -> source2: TaskSeq<'T> -> TaskSeq<'T>
val combined: TaskSeq<int>
val taskSeq: TaskSeqBuilder
<summary> Builds an asynchronous task sequence based on <see cref="IAsyncEnumerable&lt;'T&gt;" /> using computation expression syntax. </summary>
val withPrefix: TaskSeq<int>
static member TaskSeq.prependSeq: source1: 'T seq -> source2: TaskSeq<'T> -> TaskSeq<'T>
val withSuffix: TaskSeq<int>
static member TaskSeq.appendSeq: source1: TaskSeq<'T> -> source2: 'T seq -> TaskSeq<'T>
val nested: TaskSeq<TaskSeq<int>>
val flat: TaskSeq<int>
static member TaskSeq.concat: sources: TaskSeq<ResizeArray<'T>> -> TaskSeq<'T>
static member TaskSeq.concat: sources: TaskSeq<'T list> -> TaskSeq<'T>
static member TaskSeq.concat: sources: TaskSeq<'T array> -> TaskSeq<'T>
static member TaskSeq.concat: sources: TaskSeq<'T seq> -> TaskSeq<'T>
static member TaskSeq.concat: sources: TaskSeq<#TaskSeq<'T>> -> TaskSeq<'T>
val letters: TaskSeq<char>
Multiple items
val char: value: 'T -> char (requires member op_Explicit)

--------------------
type char = System.Char
val nums: TaskSeq<int>
val pairs: TaskSeq<char * int>
static member TaskSeq.zip: source1: TaskSeq<'T> -> source2: TaskSeq<'U> -> TaskSeq<'T * 'U>
val booleans: TaskSeq<bool>
type bool = System.Boolean
val triples: TaskSeq<char * int * bool>
static member TaskSeq.zip3: source1: TaskSeq<'T1> -> source2: TaskSeq<'T2> -> source3: TaskSeq<'T3> -> TaskSeq<'T1 * 'T2 * 'T3>
val consecutive: TaskSeq<int>
val pairs2: TaskSeq<int * int>
static member TaskSeq.pairwise: source: TaskSeq<'T> -> TaskSeq<'T * 'T>
val first3: TaskSeq<int>
static member TaskSeq.take: count: int -> source: TaskSeq<'T> -> TaskSeq<'T>
val atMost10: TaskSeq<int>
static member TaskSeq.truncate: count: int -> source: TaskSeq<'T> -> TaskSeq<'T>
val afterFirst2: TaskSeq<int>
static member TaskSeq.skip: count: int -> source: TaskSeq<'T> -> TaskSeq<'T>
val safeAfter10: TaskSeq<int>
static member TaskSeq.drop: count: int -> source: TaskSeq<'T> -> TaskSeq<'T>
val lessThan4: TaskSeq<int>
static member TaskSeq.takeWhile: predicate: ('T -> bool) -> source: TaskSeq<'T> -> TaskSeq<'T>
val n: int
val upToFirstGe4: TaskSeq<int>
static member TaskSeq.takeWhileInclusive: predicate: ('T -> bool) -> source: TaskSeq<'T> -> TaskSeq<'T>
val from3: TaskSeq<int>
static member TaskSeq.skipWhile: predicate: ('T -> bool) -> source: TaskSeq<'T> -> TaskSeq<'T>
val afterFirst3: TaskSeq<int>
static member TaskSeq.skipWhileInclusive: predicate: ('T -> bool) -> source: TaskSeq<'T> -> TaskSeq<'T>
val chunks: TaskSeq<int array>
static member TaskSeq.chunkBySize: chunkSize: int -> source: TaskSeq<'T> -> TaskSeq<'T array>
val windows: TaskSeq<int array>
static member TaskSeq.windowed: windowSize: int -> source: TaskSeq<'T> -> TaskSeq<'T array>

Type something to start searching.