diff --git a/release-notes.txt b/release-notes.txt
index 4de334d2..5c0b97eb 100644
--- a/release-notes.txt
+++ b/release-notes.txt
@@ -4,6 +4,9 @@ Release notes:
0.6.0
- adds TaskSeq.scan and TaskSeq.scanAsync, #289
- adds TaskSeq.pairwise, #289
+ - adds TaskSeq.groupBy and TaskSeq.groupByAsync, #289
+ - adds TaskSeq.countBy and TaskSeq.countByAsync, #289
+ - adds TaskSeq.partition and TaskSeq.partitionAsync, #289
- adds TaskSeq.reduce and TaskSeq.reduceAsync, #289
- adds TaskSeq.unfold and TaskSeq.unfoldAsync, #289
- adds TaskSeq.distinct, TaskSeq.distinctBy, TaskSeq.distinctByAsync
diff --git a/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj b/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj
index 4fdfe6fe..880459d3 100644
--- a/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj
+++ b/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj
@@ -30,6 +30,7 @@
+
diff --git a/src/FSharp.Control.TaskSeq.Test/TaskSeq.GroupBy.Tests.fs b/src/FSharp.Control.TaskSeq.Test/TaskSeq.GroupBy.Tests.fs
new file mode 100644
index 00000000..66229350
--- /dev/null
+++ b/src/FSharp.Control.TaskSeq.Test/TaskSeq.GroupBy.Tests.fs
@@ -0,0 +1,339 @@
+module TaskSeq.Tests.GroupBy
+
+open Xunit
+open FsUnit.Xunit
+
+open FSharp.Control
+
+//
+// TaskSeq.groupBy
+// TaskSeq.groupByAsync
+// TaskSeq.countBy
+// TaskSeq.countByAsync
+// TaskSeq.partition
+// TaskSeq.partitionAsync
+//
+
+module EmptySeq =
+ []
+ let ``TaskSeq-groupBy with null source raises`` () =
+ assertNullArg <| fun () -> TaskSeq.groupBy id null
+
+ assertNullArg
+ <| fun () -> TaskSeq.groupByAsync (fun x -> Task.fromResult x) null
+
+ []
+ let ``TaskSeq-countBy with null source raises`` () =
+ assertNullArg <| fun () -> TaskSeq.countBy id null
+
+ assertNullArg
+ <| fun () -> TaskSeq.countByAsync (fun x -> Task.fromResult x) null
+
+ []
+ let ``TaskSeq-partition with null source raises`` () =
+ assertNullArg
+ <| fun () -> TaskSeq.partition (fun _ -> true) null
+
+ assertNullArg
+ <| fun () -> TaskSeq.partitionAsync (fun _ -> Task.fromResult true) null
+
+ [)>]
+ let ``TaskSeq-groupBy on empty sequence returns empty array`` variant = task {
+ let! result =
+ Gen.getEmptyVariant variant
+ |> TaskSeq.groupBy (fun x -> x % 2)
+
+ result |> should be Empty
+ }
+
+ [)>]
+ let ``TaskSeq-groupByAsync on empty sequence returns empty array`` variant = task {
+ let! result =
+ Gen.getEmptyVariant variant
+ |> TaskSeq.groupByAsync (fun x -> task { return x % 2 })
+
+ result |> should be Empty
+ }
+
+ [)>]
+ let ``TaskSeq-countBy on empty sequence returns empty array`` variant = task {
+ let! result =
+ Gen.getEmptyVariant variant
+ |> TaskSeq.countBy (fun x -> x % 2)
+
+ result |> should be Empty
+ }
+
+ [)>]
+ let ``TaskSeq-countByAsync on empty sequence returns empty array`` variant = task {
+ let! result =
+ Gen.getEmptyVariant variant
+ |> TaskSeq.countByAsync (fun x -> task { return x % 2 })
+
+ result |> should be Empty
+ }
+
+ [)>]
+ let ``TaskSeq-partition on empty sequence returns two empty arrays`` variant = task {
+ let! trueItems, falseItems =
+ Gen.getEmptyVariant variant
+ |> TaskSeq.partition (fun _ -> true)
+
+ trueItems |> should be Empty
+ falseItems |> should be Empty
+ }
+
+ [)>]
+ let ``TaskSeq-partitionAsync on empty sequence returns two empty arrays`` variant = task {
+ let! trueItems, falseItems =
+ Gen.getEmptyVariant variant
+ |> TaskSeq.partitionAsync (fun _ -> Task.fromResult true)
+
+ trueItems |> should be Empty
+ falseItems |> should be Empty
+ }
+
+
+module Immutable =
+ [)>]
+ let ``TaskSeq-groupBy groups by even/odd`` variant = task {
+ let! result =
+ Gen.getSeqImmutable variant
+ |> TaskSeq.groupBy (fun x -> x % 2 = 0)
+
+ // should have exactly two groups
+ result |> Array.length |> should equal 2
+
+ let falseKey, oddItems = result[0] // 1 is first, so 'false' (odd) comes first
+ let trueKey, evenItems = result[1]
+ falseKey |> should equal false
+ trueKey |> should equal true
+ oddItems |> should equal [| 1; 3; 5; 7; 9 |]
+ evenItems |> should equal [| 2; 4; 6; 8; 10 |]
+ }
+
+ [)>]
+ let ``TaskSeq-groupByAsync groups by even/odd`` variant = task {
+ let! result =
+ Gen.getSeqImmutable variant
+ |> TaskSeq.groupByAsync (fun x -> task { return x % 2 = 0 })
+
+ result |> Array.length |> should equal 2
+
+ let falseKey, oddItems = result[0]
+ let trueKey, evenItems = result[1]
+ falseKey |> should equal false
+ trueKey |> should equal true
+ oddItems |> should equal [| 1; 3; 5; 7; 9 |]
+ evenItems |> should equal [| 2; 4; 6; 8; 10 |]
+ }
+
+ [)>]
+ let ``TaskSeq-groupBy with identity projection produces one group per element`` variant = task {
+ let! result = Gen.getSeqImmutable variant |> TaskSeq.groupBy id
+
+ result |> Array.length |> should equal 10
+
+ for key, items in result do
+ items |> should equal [| key |]
+ }
+
+ [)>]
+ let ``TaskSeq-groupBy preserves first-occurrence key ordering`` variant = task {
+ let! result =
+ Gen.getSeqImmutable variant
+ |> TaskSeq.groupBy (fun x -> x % 3)
+
+ // 1 % 3 = 1 → first key is 1
+ // 2 % 3 = 2 → second key is 2
+ // 3 % 3 = 0 → third key is 0
+ let keys = result |> Array.map fst
+ keys |> should equal [| 1; 2; 0 |]
+
+ let _, group1 = result[0] // remainder 1: 1, 4, 7, 10
+ let _, group2 = result[1] // remainder 2: 2, 5, 8
+ let _, group0 = result[2] // remainder 0: 3, 6, 9
+ group1 |> should equal [| 1; 4; 7; 10 |]
+ group2 |> should equal [| 2; 5; 8 |]
+ group0 |> should equal [| 3; 6; 9 |]
+ }
+
+ [)>]
+ let ``TaskSeq-groupBy with constant key produces single group`` variant = task {
+ let! result =
+ Gen.getSeqImmutable variant
+ |> TaskSeq.groupBy (fun _ -> "same")
+
+ result |> Array.length |> should equal 1
+ let key, items = result[0]
+ key |> should equal "same"
+ items |> should equal [| 1; 2; 3; 4; 5; 6; 7; 8; 9; 10 |]
+ }
+
+ [)>]
+ let ``TaskSeq-countBy counts by even/odd`` variant = task {
+ let! result =
+ Gen.getSeqImmutable variant
+ |> TaskSeq.countBy (fun x -> x % 2 = 0)
+
+ result |> Array.length |> should equal 2
+
+ let falseKey, oddCount = result[0]
+ let trueKey, evenCount = result[1]
+ falseKey |> should equal false
+ trueKey |> should equal true
+ oddCount |> should equal 5
+ evenCount |> should equal 5
+ }
+
+ [)>]
+ let ``TaskSeq-countByAsync counts by even/odd`` variant = task {
+ let! result =
+ Gen.getSeqImmutable variant
+ |> TaskSeq.countByAsync (fun x -> task { return x % 2 = 0 })
+
+ result |> Array.length |> should equal 2
+
+ let falseKey, oddCount = result[0]
+ let trueKey, evenCount = result[1]
+ falseKey |> should equal false
+ trueKey |> should equal true
+ oddCount |> should equal 5
+ evenCount |> should equal 5
+ }
+
+ [)>]
+ let ``TaskSeq-countBy preserves first-occurrence key ordering`` variant = task {
+ let! result =
+ Gen.getSeqImmutable variant
+ |> TaskSeq.countBy (fun x -> x % 3)
+
+ let keys = result |> Array.map fst
+ keys |> should equal [| 1; 2; 0 |]
+
+ let _, count1 = result[0] // remainder 1: 1, 4, 7, 10 → 4 items
+ let _, count2 = result[1] // remainder 2: 2, 5, 8 → 3 items
+ let _, count0 = result[2] // remainder 0: 3, 6, 9 → 3 items
+ count1 |> should equal 4
+ count2 |> should equal 3
+ count0 |> should equal 3
+ }
+
+ [)>]
+ let ``TaskSeq-countBy with constant key counts all`` variant = task {
+ let! result =
+ Gen.getSeqImmutable variant
+ |> TaskSeq.countBy (fun _ -> "same")
+
+ result |> should equal [| "same", 10 |]
+ }
+
+ [)>]
+ let ``TaskSeq-partition splits by even`` variant = task {
+ let! evens, odds =
+ Gen.getSeqImmutable variant
+ |> TaskSeq.partition (fun x -> x % 2 = 0)
+
+ evens |> should equal [| 2; 4; 6; 8; 10 |]
+ odds |> should equal [| 1; 3; 5; 7; 9 |]
+ }
+
+ [)>]
+ let ``TaskSeq-partitionAsync splits by even`` variant = task {
+ let! evens, odds =
+ Gen.getSeqImmutable variant
+ |> TaskSeq.partitionAsync (fun x -> task { return x % 2 = 0 })
+
+ evens |> should equal [| 2; 4; 6; 8; 10 |]
+ odds |> should equal [| 1; 3; 5; 7; 9 |]
+ }
+
+ [)>]
+ let ``TaskSeq-partition with always-true predicate puts all in first array`` variant = task {
+ let! trueItems, falseItems =
+ Gen.getSeqImmutable variant
+ |> TaskSeq.partition (fun _ -> true)
+
+ trueItems
+ |> should equal [| 1; 2; 3; 4; 5; 6; 7; 8; 9; 10 |]
+
+ falseItems |> should be Empty
+ }
+
+ [)>]
+ let ``TaskSeq-partition with always-false predicate puts all in second array`` variant = task {
+ let! trueItems, falseItems =
+ Gen.getSeqImmutable variant
+ |> TaskSeq.partition (fun _ -> false)
+
+ trueItems |> should be Empty
+
+ falseItems
+ |> should equal [| 1; 2; 3; 4; 5; 6; 7; 8; 9; 10 |]
+ }
+
+ [)>]
+ let ``TaskSeq-partition preserves element order within each partition`` variant = task {
+ let! trueItems, falseItems =
+ Gen.getSeqImmutable variant
+ |> TaskSeq.partition (fun x -> x <= 5)
+
+ trueItems |> should equal [| 1; 2; 3; 4; 5 |]
+ falseItems |> should equal [| 6; 7; 8; 9; 10 |]
+ }
+
+ [)>]
+ let ``TaskSeq-partitionAsync preserves element order within each partition`` variant = task {
+ let! trueItems, falseItems =
+ Gen.getSeqImmutable variant
+ |> TaskSeq.partitionAsync (fun x -> task { return x <= 5 })
+
+ trueItems |> should equal [| 1; 2; 3; 4; 5 |]
+ falseItems |> should equal [| 6; 7; 8; 9; 10 |]
+ }
+
+
+module SideEffects =
+ [)>]
+ let ``TaskSeq-groupBy groups side-effecting sequence`` variant = task {
+ let ts = Gen.getSeqWithSideEffect variant
+
+ let! result = ts |> TaskSeq.groupBy (fun x -> x % 2 = 0)
+
+ result |> Array.length |> should equal 2
+ // re-evaluating yields new side-effects (next 10 items: 11..20)
+ let! result2 = ts |> TaskSeq.groupBy (fun x -> x % 2 = 0)
+ result2 |> Array.length |> should equal 2
+ let _, group2 = result2[0]
+ group2 |> Array.sum |> should equal (11 + 13 + 15 + 17 + 19) // odd items from 11–20
+ }
+
+ [)>]
+ let ``TaskSeq-countBy counts side-effecting sequence`` variant = task {
+ let ts = Gen.getSeqWithSideEffect variant
+
+ let! result = ts |> TaskSeq.countBy (fun x -> x % 2 = 0)
+
+ // 5 odd, 5 even from 1..10
+ let falseKey, oddCount = result[0]
+ let trueKey, evenCount = result[1]
+ falseKey |> should equal false
+ trueKey |> should equal true
+ oddCount |> should equal 5
+ evenCount |> should equal 5
+ }
+
+ [)>]
+ let ``TaskSeq-partition splits side-effecting sequence`` variant = task {
+ let ts = Gen.getSeqWithSideEffect variant
+
+ let! evens, odds = ts |> TaskSeq.partition (fun x -> x % 2 = 0)
+
+ evens |> should equal [| 2; 4; 6; 8; 10 |]
+ odds |> should equal [| 1; 3; 5; 7; 9 |]
+
+ // second call picks up side effects
+ let! evens2, odds2 = ts |> TaskSeq.partition (fun x -> x % 2 = 0)
+ evens2 |> should equal [| 12; 14; 16; 18; 20 |]
+ odds2 |> should equal [| 11; 13; 15; 17; 19 |]
+ }
diff --git a/src/FSharp.Control.TaskSeq/TaskSeq.fs b/src/FSharp.Control.TaskSeq/TaskSeq.fs
index 0da32e02..795df86f 100644
--- a/src/FSharp.Control.TaskSeq/TaskSeq.fs
+++ b/src/FSharp.Control.TaskSeq/TaskSeq.fs
@@ -516,5 +516,16 @@ type TaskSeq private () =
static member scanAsync folder state source = Internal.scan (AsyncFolderAction folder) state source
static member reduce folder source = Internal.reduce (FolderAction folder) source
static member reduceAsync folder source = Internal.reduce (AsyncFolderAction folder) source
+
+ //
+ // groupBy/countBy/partition
+ //
+
+ static member groupBy projection source = Internal.groupBy (ProjectorAction projection) source
+ static member groupByAsync projection source = Internal.groupBy (AsyncProjectorAction projection) source
+ static member countBy projection source = Internal.countBy (ProjectorAction projection) source
+ static member countByAsync projection source = Internal.countBy (AsyncProjectorAction projection) source
+ static member partition predicate source = Internal.partition (Predicate predicate) source
+ static member partitionAsync predicate source = Internal.partition (PredicateAsync predicate) source
static member mapFold mapping state source = Internal.mapFold (MapFolderAction mapping) state source
static member mapFoldAsync mapping state source = Internal.mapFold (AsyncMapFolderAction mapping) state source
diff --git a/src/FSharp.Control.TaskSeq/TaskSeq.fsi b/src/FSharp.Control.TaskSeq/TaskSeq.fsi
index a0d94f8b..17f49d77 100644
--- a/src/FSharp.Control.TaskSeq/TaskSeq.fsi
+++ b/src/FSharp.Control.TaskSeq/TaskSeq.fsi
@@ -1659,6 +1659,113 @@ type TaskSeq =
/// Thrown when the input task sequence is empty.
static member reduceAsync: folder: ('T -> 'T -> #Task<'T>) -> source: TaskSeq<'T> -> Task<'T>
+ ///
+ /// Applies a key-generating function to each element of a task sequence and yields a sequence of unique keys
+ /// and arrays of all elements that have each key, in order of first occurrence of each key.
+ /// The returned array preserves the original order of elements within each group.
+ ///
+ ///
+ ///
+ /// This function consumes the entire source task sequence before returning.
+ /// If the projection function is asynchronous, consider using
+ /// .
+ ///
+ ///
+ /// A function that transforms each element into a key.
+ /// The input task sequence.
+ /// A task returning an array of (key, elements[]) pairs.
+ /// Thrown when the input task sequence is null.
+ static member groupBy: projection: ('T -> 'Key) -> source: TaskSeq<'T> -> Task<('Key * 'T[])[]> when 'Key: equality
+
+ ///
+ /// Applies an asynchronous key-generating function to each element of a task sequence and yields a sequence of
+ /// unique keys and arrays of all elements that have each key, in order of first occurrence of each key.
+ /// The returned array preserves the original order of elements within each group.
+ ///
+ ///
+ ///
+ /// This function consumes the entire source task sequence before returning.
+ /// If the projection function is synchronous, consider using
+ /// .
+ ///
+ ///
+ /// An asynchronous function that transforms each element into a key.
+ /// The input task sequence.
+ /// A task returning an array of (key, elements[]) pairs.
+ /// Thrown when the input task sequence is null.
+ static member groupByAsync:
+ projection: ('T -> #Task<'Key>) -> source: TaskSeq<'T> -> Task<('Key * 'T[])[]> when 'Key: equality
+
+ ///
+ /// Applies a key-generating function to each element of a task sequence and returns a task with an array of
+ /// unique keys and their element counts, in order of first occurrence of each key.
+ ///
+ ///
+ ///
+ /// This function consumes the entire source task sequence before returning.
+ /// If the projection function is asynchronous, consider using
+ /// .
+ ///
+ ///
+ /// A function that transforms each element into a key.
+ /// The input task sequence.
+ /// A task returning an array of (key, count) pairs.
+ /// Thrown when the input task sequence is null.
+ static member countBy: projection: ('T -> 'Key) -> source: TaskSeq<'T> -> Task<('Key * int)[]> when 'Key: equality
+
+ ///
+ /// Applies an asynchronous key-generating function to each element of a task sequence and returns a task with
+ /// an array of unique keys and their element counts, in order of first occurrence of each key.
+ ///
+ ///
+ ///
+ /// This function consumes the entire source task sequence before returning.
+ /// If the projection function is synchronous, consider using
+ /// .
+ ///
+ ///
+ /// An asynchronous function that transforms each element into a key.
+ /// The input task sequence.
+ /// A task returning an array of (key, count) pairs.
+ /// Thrown when the input task sequence is null.
+ static member countByAsync:
+ projection: ('T -> #Task<'Key>) -> source: TaskSeq<'T> -> Task<('Key * int)[]> when 'Key: equality
+
+ ///
+ /// Splits the task sequence into two arrays: those for which the given predicate returns true,
+ /// and those for which it returns false. The relative order of elements within each partition is preserved.
+ ///
+ ///
+ ///
+ /// This function consumes the entire source task sequence before returning.
+ /// If the predicate function is asynchronous, consider using
+ /// .
+ ///
+ ///
+ /// A function that returns true for elements to include in the first array.
+ /// The input task sequence.
+ /// A task returning a tuple of two arrays: (trueItems, falseItems).
+ /// Thrown when the input task sequence is null.
+ static member partition: predicate: ('T -> bool) -> source: TaskSeq<'T> -> Task<'T[] * 'T[]>
+
+ ///
+ /// Splits the task sequence into two arrays using an asynchronous predicate: those for which the predicate returns
+ /// true, and those for which it returns false. The relative order of elements within each partition
+ /// is preserved.
+ ///
+ ///
+ ///
+ /// This function consumes the entire source task sequence before returning.
+ /// If the predicate function is synchronous, consider using
+ /// .
+ ///
+ ///
+ /// An asynchronous function that returns true for elements to include in the first array.
+ /// The input task sequence.
+ /// A task returning a tuple of two arrays: (trueItems, falseItems).
+ /// Thrown when the input task sequence is null.
+ static member partitionAsync: predicate: ('T -> #Task) -> source: TaskSeq<'T> -> Task<'T[] * 'T[]>
+
///
/// Return a new task sequence with a new item inserted before the given index.
///
diff --git a/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs b/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs
index c2a920e9..4608143f 100644
--- a/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs
+++ b/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs
@@ -49,6 +49,11 @@ type internal InitAction<'T, 'TaskT when 'TaskT :> Task<'T>> =
| InitAction of init_item: (int -> 'T)
| InitActionAsync of async_init_item: (int -> 'TaskT)
+[]
+type internal ProjectorAction<'T, 'Key, 'TaskKey when 'TaskKey :> Task<'Key>> =
+ | ProjectorAction of projector: ('T -> 'Key)
+ | AsyncProjectorAction of async_projector: ('T -> 'TaskKey)
+
[]
type internal MapFolderAction<'T, 'State, 'Result, 'TaskResultState when 'TaskResultState :> Task<'Result * 'State>> =
| MapFolderAction of map_folder_action: ('State -> 'T -> 'Result * 'State)
@@ -1283,6 +1288,123 @@ module internal TaskSeqInternal =
maybePrevious <- ValueSome current
}
+ let groupBy (projector: ProjectorAction<'T, 'Key, _>) (source: TaskSeq<_>) =
+ checkNonNull (nameof source) source
+
+ task {
+ use e = source.GetAsyncEnumerator CancellationToken.None
+ let groups = Dictionary<'Key, ResizeArray<'T>>(HashIdentity.Structural)
+ let order = ResizeArray<'Key>()
+ let! step = e.MoveNextAsync()
+ let mutable go = step
+
+ match projector with
+ | ProjectorAction proj ->
+ while go do
+ let key = proj e.Current
+ let mutable ra = Unchecked.defaultof<_>
+
+ if not (groups.TryGetValue(key, &ra)) then
+ ra <- ResizeArray()
+ groups[key] <- ra
+ order.Add key
+
+ ra.Add e.Current
+ let! step = e.MoveNextAsync()
+ go <- step
+
+ | AsyncProjectorAction proj ->
+ while go do
+ let! key = proj e.Current
+ let mutable ra = Unchecked.defaultof<_>
+
+ if not (groups.TryGetValue(key, &ra)) then
+ ra <- ResizeArray()
+ groups[key] <- ra
+ order.Add key
+
+ ra.Add e.Current
+ let! step = e.MoveNextAsync()
+ go <- step
+
+ return
+ Array.init order.Count (fun i ->
+ let k = order[i]
+ k, groups[k].ToArray())
+ }
+
+ let countBy (projector: ProjectorAction<'T, 'Key, _>) (source: TaskSeq<_>) =
+ checkNonNull (nameof source) source
+
+ task {
+ use e = source.GetAsyncEnumerator CancellationToken.None
+ let counts = Dictionary<'Key, int>(HashIdentity.Structural)
+ let order = ResizeArray<'Key>()
+ let! step = e.MoveNextAsync()
+ let mutable go = step
+
+ match projector with
+ | ProjectorAction proj ->
+ while go do
+ let key = proj e.Current
+ let mutable count = 0
+
+ if not (counts.TryGetValue(key, &count)) then
+ order.Add key
+
+ counts[key] <- count + 1
+ let! step = e.MoveNextAsync()
+ go <- step
+
+ | AsyncProjectorAction proj ->
+ while go do
+ let! key = proj e.Current
+ let mutable count = 0
+
+ if not (counts.TryGetValue(key, &count)) then
+ order.Add key
+
+ counts[key] <- count + 1
+ let! step = e.MoveNextAsync()
+ go <- step
+
+ return Array.init order.Count (fun i -> let k = order[i] in k, counts[k])
+ }
+
+ let partition (predicate: PredicateAction<'T, _>) (source: TaskSeq<_>) =
+ checkNonNull (nameof source) source
+
+ task {
+ use e = source.GetAsyncEnumerator CancellationToken.None
+ let trueItems = ResizeArray<'T>()
+ let falseItems = ResizeArray<'T>()
+ let! step = e.MoveNextAsync()
+ let mutable go = step
+
+ match predicate with
+ | Predicate pred ->
+ while go do
+ let item = e.Current
+
+ if pred item then
+ trueItems.Add item
+ else
+ falseItems.Add item
+
+ let! step = e.MoveNextAsync()
+ go <- step
+
+ | PredicateAsync pred ->
+ while go do
+ let item = e.Current
+ let! result = pred item
+ if result then trueItems.Add item else falseItems.Add item
+ let! step = e.MoveNextAsync()
+ go <- step
+
+ return trueItems.ToArray(), falseItems.ToArray()
+ }
+
let chunkBySize chunkSize (source: TaskSeq<'T>) : TaskSeq<'T[]> =
if chunkSize < 1 then
invalidArg (nameof chunkSize) $"The value must be positive, but was %i{chunkSize}."