diff --git a/README.md b/README.md index 8f0b0254..71bc9b25 100644 --- a/README.md +++ b/README.md @@ -341,18 +341,19 @@ This is what has been implemented so far, is planned or skipped: | ✅ [#236][]| `removeAt` | `removeAt` | | | | ✅ [#236][]| `removeManyAt` | `removeManyAt` | | | | | `replicate` | `replicate` | | | -| ❓ | `rev` | | | [note #1](#note1 "These functions require a form of pre-materializing through 'TaskSeq.cache', similar to the approach taken in the corresponding 'Seq' functions. It doesn't make much sense to have a cached async sequence. However, 'AsyncSeq' does implement these, so we'll probably do so eventually as well.") | +| ✅ | `rev` | `rev` | | materializes sequence before yielding | | | `scan` | `scan` | `scanAsync` | | | 🚫 | `scanBack` | | | [note #2](#note2 "Because of the async nature of TaskSeq sequences, iterating from the back would be bad practice. Instead, materialize the sequence to a list or array and then apply the 'Back' iterators.") | | ✅ [#90][] | `singleton` | `singleton` | | | | ✅ [#209][]| `skip` | `skip` | | | | ✅ [#219][]| `skipWhile` | `skipWhile` | `skipWhileAsync` | | | ✅ [#219][]| | `skipWhileInclusive` | `skipWhileInclusiveAsync` | | -| ❓ | `sort` | | | [note #1](#note1 "These functions require a form of pre-materializing through 'TaskSeq.cache', similar to the approach taken in the corresponding 'Seq' functions. It doesn't make much sense to have a cached async sequence. However, 'AsyncSeq' does implement these, so we'll probably do so eventually as well.") | -| ❓ | `sortBy` | | | [note #1](#note1 "These functions require a form of pre-materializing through 'TaskSeq.cache', similar to the approach taken in the corresponding 'Seq' functions. It doesn't make much sense to have a cached async sequence. However, 'AsyncSeq' does implement these, so we'll probably do so eventually as well.") | -| ❓ | `sortByAscending` | | | [note #1](#note1 "These functions require a form of pre-materializing through 'TaskSeq.cache', similar to the approach taken in the corresponding 'Seq' functions. It doesn't make much sense to have a cached async sequence. However, 'AsyncSeq' does implement these, so we'll probably do so eventually as well.") | -| ❓ | `sortByDescending` | | | [note #1](#note1 "These functions require a form of pre-materializing through 'TaskSeq.cache', similar to the approach taken in the corresponding 'Seq' functions. It doesn't make much sense to have a cached async sequence. However, 'AsyncSeq' does implement these, so we'll probably do so eventually as well.") | -| ❓ | `sortWith` | | | [note #1](#note1 "These functions require a form of pre-materializing through 'TaskSeq.cache', similar to the approach taken in the corresponding 'Seq' functions. It doesn't make much sense to have a cached async sequence. However, 'AsyncSeq' does implement these, so we'll probably do so eventually as well.") | +| ✅ | `sort` | `sort` | | materializes sequence before yielding | +| | | `sortDescending` | | materializes sequence before yielding | +| ✅ | `sortBy` | `sortBy` | `sortByAsync` | materializes sequence before yielding | +| ✅ | `sortByAscending` | `sortBy` | `sortByAsync` | same as `sortBy`; materializes sequence before yielding | +| ✅ | `sortByDescending` | `sortByDescending` | `sortByDescendingAsync` | materializes sequence before yielding | +| ✅ | `sortWith` | `sortWith` | | materializes sequence before yielding | | | `splitInto` | `splitInto` | | | | | `sum` | `sum` | | | | | `sumBy` | `sumBy` | `sumByAsync` | | diff --git a/global.json b/global.json index badcd443..cff8de89 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "10.0.103", + "version": "10.0.102", "rollForward": "minor" } } diff --git a/release-notes.txt b/release-notes.txt index 602d3dd2..20740f28 100644 --- a/release-notes.txt +++ b/release-notes.txt @@ -5,6 +5,8 @@ Release notes: - update engineering to .NET 9/10 - adds TaskSeq.scan and TaskSeq.scanAsync, #289 - adds TaskSeq.pairwise, #289 + - adds TaskSeq.rev, sort, sortDescending, sortBy, sortByAsync, + sortByDescending, sortByDescendingAsync, sortWith - fixes: CancellationToken passed to GetAsyncEnumerator is now honored in MoveNextAsync, #179 0.4.0 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 4b57dda2..351503de 100644 --- a/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj +++ b/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj @@ -21,6 +21,7 @@ + diff --git a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Sort.Tests.fs b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Sort.Tests.fs new file mode 100644 index 00000000..4289d6a0 --- /dev/null +++ b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Sort.Tests.fs @@ -0,0 +1,370 @@ +module TaskSeq.Tests.Sort + +open System.Threading.Tasks +open Xunit +open FsUnit.Xunit + +open FSharp.Control + +// +// TaskSeq.rev / TaskSeq.sort / TaskSeq.sortDescending +// TaskSeq.sortBy / TaskSeq.sortByAsync +// TaskSeq.sortByDescending / TaskSeq.sortByDescendingAsync +// TaskSeq.sortWith +// + +module Rev = + module EmptySeq = + [] + let ``TaskSeq-rev with null source raises`` () = assertNullArg <| fun () -> TaskSeq.rev null + + [)>] + let ``TaskSeq-rev on empty returns empty`` variant = Gen.getEmptyVariant variant |> TaskSeq.rev |> verifyEmpty + + module Immutable = + [] + let ``TaskSeq-rev on single element returns same element`` () = task { + let! result = taskSeq { yield 42 } |> TaskSeq.rev |> TaskSeq.toListAsync + result |> should equal [ 42 ] + } + + [] + let ``TaskSeq-rev reverses ascending sequence`` () = task { + let! result = + taskSeq { yield! [ 1..5 ] } + |> TaskSeq.rev + |> TaskSeq.toListAsync + + result |> should equal [ 5; 4; 3; 2; 1 ] + } + + [] + let ``TaskSeq-rev of rev is identity`` () = task { + let! result = + taskSeq { yield! [ 10; 20; 30 ] } + |> TaskSeq.rev + |> TaskSeq.rev + |> TaskSeq.toListAsync + + result |> should equal [ 10; 20; 30 ] + } + + [)>] + let ``TaskSeq-rev all variants - correct length and reversed order`` variant = task { + let! result = + Gen.getSeqImmutable variant + |> TaskSeq.rev + |> TaskSeq.toListAsync + + result |> List.length |> should equal 10 + result |> List.head |> should equal 10 + result |> List.last |> should equal 1 + } + +module Sort = + module EmptySeq = + [] + let ``TaskSeq-sort with null source raises`` () = assertNullArg <| fun () -> TaskSeq.sort null + + [)>] + let ``TaskSeq-sort on empty returns empty`` variant = Gen.getEmptyVariant variant |> TaskSeq.sort |> verifyEmpty + + module Immutable = + [] + let ``TaskSeq-sort on single element returns same element`` () = task { + let! result = taskSeq { yield 42 } |> TaskSeq.sort |> TaskSeq.toListAsync + result |> should equal [ 42 ] + } + + [] + let ``TaskSeq-sort sorts integers ascending`` () = task { + let! result = + taskSeq { yield! [ 3; 1; 4; 1; 5; 9; 2; 6 ] } + |> TaskSeq.sort + |> TaskSeq.toListAsync + + result |> should equal [ 1; 1; 2; 3; 4; 5; 6; 9 ] + } + + [] + let ``TaskSeq-sort sorts strings lexicographically`` () = task { + let! result = + taskSeq { yield! [ "banana"; "apple"; "cherry" ] } + |> TaskSeq.sort + |> TaskSeq.toListAsync + + result |> should equal [ "apple"; "banana"; "cherry" ] + } + + [] + let ``TaskSeq-sort already-sorted sequence stays sorted`` () = task { + let! result = + taskSeq { yield! [ 1..10 ] } + |> TaskSeq.sort + |> TaskSeq.toListAsync + + result |> should equal [ 1..10 ] + } + +module SortDescending = + module EmptySeq = + [] + let ``TaskSeq-sortDescending with null source raises`` () = assertNullArg <| fun () -> TaskSeq.sortDescending null + + [)>] + let ``TaskSeq-sortDescending on empty returns empty`` variant = + Gen.getEmptyVariant variant + |> TaskSeq.sortDescending + |> verifyEmpty + + module Immutable = + [] + let ``TaskSeq-sortDescending sorts integers descending`` () = task { + let! result = + taskSeq { yield! [ 3; 1; 4; 1; 5; 9; 2; 6 ] } + |> TaskSeq.sortDescending + |> TaskSeq.toListAsync + + result |> should equal [ 9; 6; 5; 4; 3; 2; 1; 1 ] + } + + [] + let ``TaskSeq-sortDescending is reverse of sort`` () = task { + let input = [ 3; 1; 4; 1; 5; 9; 2; 6 ] + + let! ascending = + taskSeq { yield! input } + |> TaskSeq.sort + |> TaskSeq.toListAsync + + let! descending = + taskSeq { yield! input } + |> TaskSeq.sortDescending + |> TaskSeq.toListAsync + + descending |> should equal (List.rev ascending) + } + +module SortBy = + module EmptySeq = + [] + let ``TaskSeq-sortBy with null source raises`` () = assertNullArg <| fun () -> TaskSeq.sortBy id null + + [)>] + let ``TaskSeq-sortBy on empty returns empty`` variant = + Gen.getEmptyVariant variant + |> TaskSeq.sortBy id + |> verifyEmpty + + module Immutable = + [] + let ``TaskSeq-sortBy sorts by string length`` () = task { + let! result = + taskSeq { yield! [ "banana"; "fig"; "apple"; "kiwi" ] } + |> TaskSeq.sortBy String.length + |> TaskSeq.toListAsync + + result + |> List.map String.length + |> should equal [ 3; 4; 5; 6 ] + } + + [] + let ``TaskSeq-sortBy sorts tuples by key`` () = task { + let! result = + taskSeq { yield! [ ("b", 2); ("a", 1); ("c", 3) ] } + |> TaskSeq.sortBy fst + |> TaskSeq.toListAsync + + result |> List.map fst |> should equal [ "a"; "b"; "c" ] + } + + [] + let ``TaskSeq-sortBy with identity is equivalent to sort`` () = task { + let input = [ 5; 3; 8; 1; 4 ] + + let! sorted = + taskSeq { yield! input } + |> TaskSeq.sort + |> TaskSeq.toListAsync + + let! sortedById = + taskSeq { yield! input } + |> TaskSeq.sortBy id + |> TaskSeq.toListAsync + + sortedById |> should equal sorted + } + +module SortByDescending = + module EmptySeq = + [] + let ``TaskSeq-sortByDescending with null source raises`` () = assertNullArg <| fun () -> TaskSeq.sortByDescending id null + + [)>] + let ``TaskSeq-sortByDescending on empty returns empty`` variant = + Gen.getEmptyVariant variant + |> TaskSeq.sortByDescending id + |> verifyEmpty + + module Immutable = + [] + let ``TaskSeq-sortByDescending sorts by string length descending`` () = task { + let! result = + taskSeq { yield! [ "banana"; "fig"; "apple"; "kiwi" ] } + |> TaskSeq.sortByDescending String.length + |> TaskSeq.toListAsync + + result + |> List.map String.length + |> should equal [ 6; 5; 4; 3 ] + } + + [] + let ``TaskSeq-sortByDescending is reverse of sortBy for same projection`` () = task { + let input = [ "banana"; "fig"; "apple"; "kiwi" ] + + let! ascending = + taskSeq { yield! input } + |> TaskSeq.sortBy String.length + |> TaskSeq.toListAsync + + let! descending = + taskSeq { yield! input } + |> TaskSeq.sortByDescending String.length + |> TaskSeq.toListAsync + + descending |> should equal (List.rev ascending) + } + +module SortByAsync = + module EmptySeq = + [] + let ``TaskSeq-sortByAsync with null source raises`` () = + assertNullArg + <| fun () -> TaskSeq.sortByAsync (fun x -> Task.FromResult x) null + + [)>] + let ``TaskSeq-sortByAsync on empty returns empty`` variant = task { + do! + Gen.getEmptyVariant variant + |> TaskSeq.sortByAsync (fun x -> Task.FromResult x) + |> verifyEmpty + } + + module Immutable = + [] + let ``TaskSeq-sortByAsync sorts by async key`` () = task { + let! result = + taskSeq { yield! [ "banana"; "fig"; "apple"; "kiwi" ] } + |> TaskSeq.sortByAsync (fun s -> Task.FromResult(String.length s)) + |> TaskSeq.toListAsync + + result + |> List.map String.length + |> should equal [ 3; 4; 5; 6 ] + } + + [] + let ``TaskSeq-sortByAsync result matches synchronous sortBy`` () = task { + let input = [ 5; 3; 8; 1; 4 ] + + let! syncResult = + taskSeq { yield! input } + |> TaskSeq.sortBy id + |> TaskSeq.toListAsync + + let! asyncResult = + taskSeq { yield! input } + |> TaskSeq.sortByAsync (fun x -> Task.FromResult x) + |> TaskSeq.toListAsync + + asyncResult |> should equal syncResult + } + +module SortByDescendingAsync = + module EmptySeq = + [] + let ``TaskSeq-sortByDescendingAsync with null source raises`` () = + assertNullArg + <| fun () -> TaskSeq.sortByDescendingAsync (fun x -> Task.FromResult x) null + + [)>] + let ``TaskSeq-sortByDescendingAsync on empty returns empty`` variant = task { + do! + Gen.getEmptyVariant variant + |> TaskSeq.sortByDescendingAsync (fun x -> Task.FromResult x) + |> verifyEmpty + } + + module Immutable = + [] + let ``TaskSeq-sortByDescendingAsync sorts descending by async key`` () = task { + let! result = + taskSeq { yield! [ "banana"; "fig"; "apple"; "kiwi" ] } + |> TaskSeq.sortByDescendingAsync (fun s -> Task.FromResult(String.length s)) + |> TaskSeq.toListAsync + + result + |> List.map String.length + |> should equal [ 6; 5; 4; 3 ] + } + + [] + let ``TaskSeq-sortByDescendingAsync result matches sortByDescending`` () = task { + let input = [ 5; 3; 8; 1; 4 ] + + let! syncResult = + taskSeq { yield! input } + |> TaskSeq.sortByDescending id + |> TaskSeq.toListAsync + + let! asyncResult = + taskSeq { yield! input } + |> TaskSeq.sortByDescendingAsync (fun x -> Task.FromResult x) + |> TaskSeq.toListAsync + + asyncResult |> should equal syncResult + } + +module SortWith = + module EmptySeq = + [] + let ``TaskSeq-sortWith with null source raises`` () = assertNullArg <| fun () -> TaskSeq.sortWith compare null + + [)>] + let ``TaskSeq-sortWith on empty returns empty`` variant = + Gen.getEmptyVariant variant + |> TaskSeq.sortWith compare + |> verifyEmpty + + module Immutable = + [] + let ``TaskSeq-sortWith with standard compare gives ascending order`` () = task { + let! result = + taskSeq { yield! [ 3; 1; 4; 1; 5; 9; 2; 6 ] } + |> TaskSeq.sortWith compare + |> TaskSeq.toListAsync + + result |> should equal [ 1; 1; 2; 3; 4; 5; 6; 9 ] + } + + [] + let ``TaskSeq-sortWith with reversed compare gives descending order`` () = task { + let! result = + taskSeq { yield! [ 3; 1; 4; 1; 5; 9; 2; 6 ] } + |> TaskSeq.sortWith (fun a b -> compare b a) + |> TaskSeq.toListAsync + + result |> should equal [ 9; 6; 5; 4; 3; 2; 1; 1 ] + } + + [] + let ``TaskSeq-sortWith with custom comparer sorts by absolute value`` () = task { + let! result = + taskSeq { yield! [ -3; 1; -4; 2; -5 ] } + |> TaskSeq.sortWith (fun a b -> compare (abs a) (abs b)) + |> TaskSeq.toListAsync + + result |> List.map abs |> should equal [ 1; 2; 3; 4; 5 ] + } diff --git a/src/FSharp.Control.TaskSeq/TaskSeq.fs b/src/FSharp.Control.TaskSeq/TaskSeq.fs index 0ff387c6..7786558c 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeq.fs +++ b/src/FSharp.Control.TaskSeq/TaskSeq.fs @@ -414,3 +414,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 + + // + // sort/rev functions (require full materialisation before streaming) + // + + static member rev source = Internal.rev source + static member sort source = Internal.sort source + static member sortDescending source = Internal.sortDescending source + static member sortBy projection source = Internal.sortBy projection source + static member sortByDescending projection source = Internal.sortByDescending projection source + static member sortByAsync projection source = Internal.sortByAsync projection source + static member sortByDescendingAsync projection source = Internal.sortByDescendingAsync projection source + static member sortWith comparer source = Internal.sortWith comparer source diff --git a/src/FSharp.Control.TaskSeq/TaskSeq.fsi b/src/FSharp.Control.TaskSeq/TaskSeq.fsi index d1d8d7c9..44ec6500 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeq.fsi +++ b/src/FSharp.Control.TaskSeq/TaskSeq.fsi @@ -1511,3 +1511,97 @@ type TaskSeq = /// Thrown when the input task sequence is null. /// Thrown when index is below 0 or greater than source length. static member updateAt: index: int -> value: 'T -> source: TaskSeq<'T> -> TaskSeq<'T> + + /// + /// Returns a new task sequence with elements in reverse order. This function requires the full sequence to be + /// materialised into memory before streaming the results back. + /// + /// + /// The input task sequence. + /// The reversed task sequence. + /// Thrown when the input task sequence is null. + static member rev: source: TaskSeq<'T> -> TaskSeq<'T> + + /// + /// Sorts the elements of a task sequence in ascending order. This function requires the full sequence to be + /// materialised into memory before streaming the sorted results back. + /// If the projection function is asynchronous, consider using . + /// + /// + /// The input task sequence. + /// The sorted task sequence. + /// Thrown when the input task sequence is null. + static member sort<'T when 'T: comparison> : source: TaskSeq<'T> -> TaskSeq<'T> + + /// + /// Sorts the elements of a task sequence in descending order. This function requires the full sequence to be + /// materialised into memory before streaming the sorted results back. + /// + /// + /// The input task sequence. + /// The sorted task sequence. + /// Thrown when the input task sequence is null. + static member sortDescending<'T when 'T: comparison> : source: TaskSeq<'T> -> TaskSeq<'T> + + /// + /// Sorts the elements of a task sequence in ascending order by the given projection. This function requires the + /// full sequence to be materialised into memory before streaming the sorted results back. + /// If the projection function is asynchronous, consider using . + /// + /// + /// A function to transform elements of the input sequence into the type being compared. + /// The input task sequence. + /// The sorted task sequence. + /// Thrown when the input task sequence is null. + static member sortBy<'T, 'Key when 'Key: comparison> : + projection: ('T -> 'Key) -> source: TaskSeq<'T> -> TaskSeq<'T> + + /// + /// Sorts the elements of a task sequence in descending order by the given projection. This function requires the + /// full sequence to be materialised into memory before streaming the sorted results back. + /// If the projection function is asynchronous, consider using . + /// + /// + /// A function to transform elements of the input sequence into the type being compared. + /// The input task sequence. + /// The sorted task sequence. + /// Thrown when the input task sequence is null. + static member sortByDescending<'T, 'Key when 'Key: comparison> : + projection: ('T -> 'Key) -> source: TaskSeq<'T> -> TaskSeq<'T> + + /// + /// Sorts the elements of a task sequence in ascending order by the given asynchronous projection. This function + /// requires the full sequence to be materialised into memory before streaming the sorted results back. + /// If the projection function is synchronous, consider using . + /// + /// + /// An asynchronous function to transform elements of the input sequence into the type being compared. + /// The input task sequence. + /// The sorted task sequence. + /// Thrown when the input task sequence is null. + static member sortByAsync<'T, 'Key when 'Key: comparison> : + projection: ('T -> Task<'Key>) -> source: TaskSeq<'T> -> TaskSeq<'T> + + /// + /// Sorts the elements of a task sequence in descending order by the given asynchronous projection. This function + /// requires the full sequence to be materialised into memory before streaming the sorted results back. + /// If the projection function is synchronous, consider using . + /// + /// + /// An asynchronous function to transform elements of the input sequence into the type being compared. + /// The input task sequence. + /// The sorted task sequence. + /// Thrown when the input task sequence is null. + static member sortByDescendingAsync<'T, 'Key when 'Key: comparison> : + projection: ('T -> Task<'Key>) -> source: TaskSeq<'T> -> TaskSeq<'T> + + /// + /// Sorts the elements of a task sequence using the given comparison function. This function requires the full + /// sequence to be materialised into memory before streaming the sorted results back. + /// + /// + /// A function that compares two elements and returns an integer indicating their relative order. + /// The input task sequence. + /// The sorted task sequence. + /// Thrown when the input task sequence is null. + static member sortWith: comparer: ('T -> 'T -> int) -> source: TaskSeq<'T> -> TaskSeq<'T> diff --git a/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs b/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs index ee7b3df0..aee2877d 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs +++ b/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs @@ -1152,3 +1152,92 @@ module internal TaskSeqInternal = yield previous, current maybePrevious <- ValueSome current } + + let rev (source: TaskSeq<_>) = + checkNonNull (nameof source) source + + taskSeq { + let! resizeArr = toResizeArrayAsync source + let arr = resizeArr.ToArray() + + for i = arr.Length - 1 downto 0 do + yield arr.[i] + } + + let sort (source: TaskSeq<_>) = + checkNonNull (nameof source) source + + taskSeq { + let! resizeArr = toResizeArrayAsync source + yield! resizeArr.ToArray() |> Array.sort + } + + let sortDescending (source: TaskSeq<_>) = + checkNonNull (nameof source) source + + taskSeq { + let! resizeArr = toResizeArrayAsync source + yield! resizeArr.ToArray() |> Array.sortDescending + } + + let sortBy (projection: 'T -> 'Key) (source: TaskSeq<'T>) = + checkNonNull (nameof source) source + + taskSeq { + let! resizeArr = toResizeArrayAsync source + yield! resizeArr.ToArray() |> Array.sortBy projection + } + + let sortByDescending (projection: 'T -> 'Key) (source: TaskSeq<'T>) = + checkNonNull (nameof source) source + + taskSeq { + let! resizeArr = toResizeArrayAsync source + yield! resizeArr.ToArray() |> Array.sortByDescending projection + } + + let sortByAsync (projection: 'T -> Task<'Key>) (source: TaskSeq<'T>) = + checkNonNull (nameof source) source + + taskSeq { + let! pairs = task { + let! resizeArr = toResizeArrayAsync source + let arr = resizeArr.ToArray() + let kvs = ResizeArray(arr.Length) + + for item in arr do + let! k = projection item + kvs.Add(k, item) + + return kvs.ToArray() + } + + yield! pairs |> Array.sortBy fst |> Array.map snd + } + + let sortByDescendingAsync (projection: 'T -> Task<'Key>) (source: TaskSeq<'T>) = + checkNonNull (nameof source) source + + taskSeq { + let! pairs = task { + let! resizeArr = toResizeArrayAsync source + let arr = resizeArr.ToArray() + let kvs = ResizeArray(arr.Length) + + for item in arr do + let! k = projection item + kvs.Add(k, item) + + return kvs.ToArray() + } + + yield! pairs |> Array.sortByDescending fst |> Array.map snd + } + + let sortWith (comparer: 'T -> 'T -> int) (source: TaskSeq<'T>) = + checkNonNull (nameof source) source + + taskSeq { + let! resizeArr = toResizeArrayAsync source + yield! resizeArr.ToArray() |> Array.sortWith comparer + }