From 6d93143ce9a86efffa8303102445d2aa0e90869e Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Wed, 20 Dec 2023 23:31:42 +0100 Subject: [PATCH 1/5] Fix minor glitch in documentation --- src/FSharp.Control.TaskSeq/TaskSeq.fsi | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/FSharp.Control.TaskSeq/TaskSeq.fsi b/src/FSharp.Control.TaskSeq/TaskSeq.fsi index 3c14581d..d9de462d 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeq.fsi +++ b/src/FSharp.Control.TaskSeq/TaskSeq.fsi @@ -728,8 +728,8 @@ type TaskSeq = /// /// Returns a task sequence that, when iterated, skips elements of the underlying /// sequence, and then yields the remainder. Raises an exception if there are not - /// items. See for a version that does not raise an exception. - /// See also for the inverse of this operation. + /// items. See for a version that does not raise an exception. + /// See also for the inverse of this operation. /// /// /// The number of items to skip. @@ -746,8 +746,8 @@ type TaskSeq = /// /// Returns a task sequence that, when iterated, drops at most elements of the /// underlying sequence, and then returns the remainder of the elements, if any. - /// See for a version that raises an exception if there - /// are not enough elements. See also for the inverse of this operation. + /// See for a version that raises an exception if there + /// are not enough elements. See also for the inverse of this operation. /// /// /// The maximum number of items to drop. @@ -760,8 +760,8 @@ type TaskSeq = /// /// Returns a task sequence that, when iterated, yields elements of the /// underlying sequence, and then returns no further elements. Raises an exception if there are not enough - /// elements in the sequence. See for a version that does not raise an exception. - /// See also for the inverse of this operation. + /// elements in the sequence. See for a version that does not raise an exception. + /// See also for the inverse of this operation. /// /// /// The number of items to take. @@ -777,8 +777,8 @@ type TaskSeq = /// /// Returns a task sequence that, when iterated, yields at most elements of the underlying /// sequence, truncating the remainder, if any. - /// See for a version that raises an exception if there are not enough elements in the - /// sequence. See also for the inverse of this operation. + /// See for a version that raises an exception if there are not enough elements in the + /// sequence. See also for the inverse of this operation. /// /// /// The maximum number of items to enumerate. From 36b007ae0ab74fbe6a0da18334b6015fa4544db5 Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Wed, 20 Dec 2023 23:54:36 +0100 Subject: [PATCH 2/5] Fix release notes for `take`, `drop`, `truncate`, `skip` --- release-notes.txt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/release-notes.txt b/release-notes.txt index 8d13122e..ebd40c4c 100644 --- a/release-notes.txt +++ b/release-notes.txt @@ -1,10 +1,14 @@ Release notes: 0.4.x (unreleased) - - overhaul all doc comments, add exceptions, improve editor experience, #136 + - overhaul all doc comments, add exceptions, improve IDE quick-info experience, #136 + - new surface area functions, fixes #208: + * TaskSeq.take, TaskSeq.skip, #209 + * TaskSeq.truncate, TaskSeq.drop, #209 + - Performance: less thread hops with 'StartImmediateAsTask' instead of 'StartAsTask', fixes #135 - BINARY INCOMPATIBILITY: 'TaskSeq' module is now static members on 'TaskSeq<_>', fixes #184 - - DEPRECATIONS (these raise warning FS0044): + - DEPRECATIONS (warning FS0044): - type 'taskSeq<_>' is renamed to 'TaskSeq<_>', fixes #193 - function 'ValueTask.ofIValueTaskSource` renamed to `ValueTask.ofSource`, fixes #193 - function `ValueTask.FromResult` is renamed to `ValueTask.fromResult`, fixes #193 From 3323fd90ed592cf60201f952324d89bdb58d125c Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Wed, 20 Dec 2023 23:58:01 +0100 Subject: [PATCH 3/5] Add instruction for next time the nuget token is expired --- .github/workflows/publish.yaml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 4b780bf4..18c61e7b 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -26,7 +26,10 @@ jobs: - name: Nuget publish # skip-duplicate ensures that the 409 error received when the package was already published, # will just issue a warning and won't have the GH action fail. - # NUGET_PUBLISH_TOKEN_TASKSEQ is valid until approx. 8 Nov 2023 and will need to be updated by then. - # do so under https://github.com/fsprojects/FSharp.Control.TaskSeq/settings/secrets/actions - # select button "Add repository secret" or update the existing one under "Repository secrets" + # NUGET_PUBLISH_TOKEN_TASKSEQ is valid until approx. 11 Dec 2024 and will need to be updated by then: + # - log in to Nuget.org using 'abelbraaksma' admin account and then refresh the token in Nuget + # - copy the token + # - go to https://github.com/fsprojects/FSharp.Control.TaskSeq/settings/secrets/actions + # - select button "Add repository secret" or update the existing one under "Repository secrets" + # - rerun the job run: dotnet nuget push packages\FSharp.Control.TaskSeq.*.nupkg --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_PUBLISH_TOKEN_TASKSEQ }} --skip-duplicate From 3f1c1fd3902f07b2ea4404ef9f80059738f4b12a Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Thu, 21 Dec 2023 00:18:50 +0100 Subject: [PATCH 4/5] Update `readme.md` with new functions and fix old stuff --- README.md | 286 +++++++++++++++++++++++++++--------------------------- 1 file changed, 145 insertions(+), 141 deletions(-) diff --git a/README.md b/README.md index dbfdcd12..cdf279f7 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ As with `seq` and `Seq`, this library comes with a bunch of well-known collectio ### `taskSeq` computation expressions The `taskSeq` computation expression can be used just like using `seq`. -Additionally, it adds support for working with `Task`s through `let!` and +Additionally, it adds support for working with `Task`s through `let!` and looping over both normal and asynchronous sequences (ones that implement `IAsyncEnumerable<'T>'`). You can use `yield!` and `yield` and there's support for `use` and `use!`, `try-with` and `try-finally` and `while` loops within @@ -127,7 +127,7 @@ let helloTs = taskSeq { yield "Hello, World!" } let f() = task { // using toList forces execution of whole sequence let! hello = TaskSeq.toList helloTs // toList returns a Task<'T list> - return List.head hello + return List.head hello } // can be mixed with normal sequences @@ -147,15 +147,15 @@ let allFilesAsLines() = taskSeq { yield! contents } -let write file = +let write file = allFilesAsLines() - + // synchronous map function on asynchronous task sequence |> TaskSeq.map (fun x -> x.Replace("a", "b")) // asynchronous map |> TaskSeq.mapAsync (fun x -> task { return "hello: " + x }) - + // asynchronous iter |> TaskSeq.iterAsync (fun data -> File.WriteAllTextAsync(fileName, data)) @@ -227,142 +227,141 @@ We are working hard on getting a full set of module functions on `TaskSeq` that The following is the progress report: -| Done | `Seq` | `TaskSeq` | Variants | Remarks | -|------------------|--------------------|-----------------|----------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| ❓ | `allPairs` | `allPairs` | | [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.") | -| ✅ [#81][] | `append` | `append` | | | -| ✅ [#81][] | | | `appendSeq` | | -| ✅ [#81][] | | | `prependSeq` | | -| | `average` | `average` | | | -| | `averageBy` | `averageBy` | `averageByAsync` | | -| ❓ | `cache` | `cache` | | [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.") | -| ✅ [#67][] | `cast` | `cast` | | | -| ✅ [#67][] | | | `box` | | -| ✅ [#67][] | | | `unbox` | | -| ✅ [#23][] | `choose` | `choose` | `chooseAsync` | | -| | `chunkBySize` | `chunkBySize` | | | -| ✅ [#11][] | `collect` | `collect` | `collectAsync` | | -| ✅ [#11][] | | `collectSeq` | `collectSeqAsync` | | -| | `compareWith` | `compareWith` | `compareWithAsync` | | -| ✅ [#69][] | `concat` | `concat` | | | -| ✅ [#70][] | `contains` | `contains` | | | -| ✅ [#82][] | `delay` | `delay` | | | -| | `distinct` | `distinct` | | | -| | `distinctBy` | `dictinctBy` | `distinctByAsync` | | -| ✅ [#2][] | `empty` | `empty` | | | -| ✅ [#23][] | `exactlyOne` | `exactlyOne` | | | -| ✅ [#83][] | `except` | `except` | | | -| ✅ [#83][] | | `exceptOfSeq` | | | -| ✅ [#70][] | `exists` | `exists` | `existsAsync` | | -| | `exists2` | `exists2` | | | -| ✅ [#23][] | `filter` | `filter` | `filterAsync` | | -| ✅ [#23][] | `find` | `find` | `findAsync` | | -| 🚫 | `findBack` | | | [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.") | -| ✅ [#68][] | `findIndex` | `findIndex` | `findIndexAsync` | | -| 🚫 | `findIndexBack` | n/a | n/a | [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.") | -| ✅ [#2][] | `fold` | `fold` | `foldAsync` | | -| | `fold2` | `fold2` | `fold2Async` | | -| 🚫 | `foldBack` | | | [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.") | -| 🚫 | `foldBack2` | | | [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.") | -| | `forall` | `forall` | `forallAsync` | | -| | `forall2` | `forall2` | `forall2Async` | | -| ❓ | `groupBy` | `groupBy` | `groupByAsync` | [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.") | -| ✅ [#23][] | `head` | `head` | | | -| ✅ [#68][] | `indexed` | `indexed` | | | -| ✅ [#69][] | `init` | `init` | `initAsync` | | -| ✅ [#69][] | `initInfinite` | `initInfinite` | `initInfiniteAsync` | | -| | `insertAt` | `insertAt` | | | -| | `insertManyAt` | `insertManyAt` | | | -| ✅ [#23][] | `isEmpty` | `isEmpty` | | | -| ✅ [#23][] | `item` | `item` | | | -| ✅ [#2][] | `iter` | `iter` | `iterAsync` | | -| | `iter2` | `iter2` | `iter2Async` | | -| ✅ [#2][] | `iteri` | `iteri` | `iteriAsync` | | -| | `iteri2` | `iteri2` | `iteri2Async` | | -| ✅ [#23][] | `last` | `last` | | | -| ✅ [#53][] | `length` | `length` | | | -| ✅ [#53][] | | `lengthBy` | `lengthByAsync` | | -| ✅ [#2][] | `map` | `map` | `mapAsync` | | -| | `map2` | `map2` | `map2Async` | | -| | `map3` | `map3` | `map3Async` | | -| | `mapFold` | `mapFold` | `mapFoldAsync` | | -| 🚫 | `mapFoldBack` | | | [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.") | -| ✅ [#2][] | `mapi` | `mapi` | `mapiAsync` | | -| | `mapi2` | `mapi2` | `mapi2Async` | | -| | `max` | `max` | | | -| | `maxBy` | `maxBy` | `maxByAsync` | | -| | `min` | `min` | | | -| | `minBy` | `minBy` | `minByAsync` | | -| ✅ [#2][] | `ofArray` | `ofArray` | | | -| ✅ [#2][] | | `ofAsyncArray` | | | -| ✅ [#2][] | | `ofAsyncList` | | | -| ✅ [#2][] | | `ofAsyncSeq` | | | -| ✅ [#2][] | `ofList` | `ofList` | | | -| ✅ [#2][] | | `ofTaskList` | | | -| ✅ [#2][] | | `ofResizeArray` | | | -| ✅ [#2][] | | `ofSeq` | | | -| ✅ [#2][] | | `ofTaskArray` | | | -| ✅ [#2][] | | `ofTaskList` | | | -| ✅ [#2][] | | `ofTaskSeq` | | | -| | `pairwise` | `pairwise` | | | -| | `permute` | `permute` | `permuteAsync` | | -| ✅ [#23][] | `pick` | `pick` | `pickAsync` | | -| 🚫 | `readOnly` | | | [note #3](#note3 "The motivation for 'readOnly' in 'Seq' is that a cast from a mutable array or list to a 'seq<_>' is valid and can be cast back, leading to a mutable sequence. Since 'TaskSeq' doesn't implement 'IEnumerable<_>', such casts are not possible.") | -| | `reduce` | `reduce` | `reduceAsync` | | -| 🚫 | `reduceBack` | | | [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.") | -| | `removeAt` | `removeAt` | | | -| | `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.") | -| | `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` | | | -| | `skip` | `skip` | | | -| | `skipWhile` | `skipWhile` | `skipWhileAsync` | | -| | | | `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.") | -| | `splitInto` | `splitInto` | | | -| | `sum` | `sum` | | | -| | `sumBy` | `sumBy` | `sumByAsync` | | -| ✅ [#76][] | `tail` | `tail` | | | -| | `take` | `take` | | | -| ✅ [#126][]| `takeWhile` | `takeWhile` | `takeWhileAsync` | | -| ✅ [#126][]| | | `takeWhileInclusive` | | -| ✅ [#126][]| | | `takeWhileInclusiveAsync`| | -| ✅ [#2][] | `toArray` | `toArray` | `toArrayAsync` | | -| ✅ [#2][] | | `toIList` | `toIListAsync` | | -| ✅ [#2][] | `toList` | `toList` | `toListAsync` | | -| ✅ [#2][] | | `toResizeArray` | `toResizeArrayAsync` | | -| ✅ [#2][] | | `toSeq` | `toSeqAsync` | | -| | | […] | | | -| ❓ | `transpose` | | | [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.") | -| | `truncate` | `truncate` | | | -| ✅ [#23][] | `tryExactlyOne` | `tryExactlyOne` | `tryExactlyOneAsync` | | -| ✅ [#23][] | `tryFind` | `tryFind` | `tryFindAsync` | | -| 🚫 | `tryFindBack` | | | [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.") | -| ✅ [#68][] | `tryFindIndex` | `tryFindIndex` | `tryFindIndexAsync` | | -| 🚫 | `tryFindIndexBack` | | | [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.") | -| ✅ [#23][] | `tryHead` | `tryHead` | | | -| ✅ [#23][] | `tryItem` | `tryItem` | | | -| ✅ [#23][] | `tryLast` | `tryLast` | | | -| ✅ [#23][] | `tryPick` | `tryPick` | `tryPickAsync` | | -| ✅ [#76][] | | `tryTail` | | | -| | `unfold` | `unfold` | `unfoldAsync` | | -| | `updateAt` | `updateAt` | | | -| | `where` | `where` | `whereAsync` | | -| | `windowed` | `windowed` | | | -| ✅ [#2][] | `zip` | `zip` | | | -| | `zip3` | `zip3` | | | -| | | `zip4` | | | - - -¹⁾ _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._ -²⁾ _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 `xxxBack` iterators._ +| Done | `Seq` | `TaskSeq` | Variants | Remarks | +|------------------|--------------------|----------------------|---------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| ❓ | `allPairs` | `allPairs` | | [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.") | +| ✅ [#81][] | `append` | `append` | | | +| ✅ [#81][] | | | `appendSeq` | | +| ✅ [#81][] | | | `prependSeq` | | +| | `average` | `average` | | | +| | `averageBy` | `averageBy` | `averageByAsync` | | +| ❓ | `cache` | `cache` | | [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.") | +| ✅ [#67][] | `cast` | `cast` | | | +| ✅ [#67][] | | | `box` | | +| ✅ [#67][] | | | `unbox` | | +| ✅ [#23][] | `choose` | `choose` | `chooseAsync` | | +| | `chunkBySize` | `chunkBySize` | | | +| ✅ [#11][] | `collect` | `collect` | `collectAsync` | | +| ✅ [#11][] | | `collectSeq` | `collectSeqAsync` | | +| | `compareWith` | `compareWith` | `compareWithAsync` | | +| ✅ [#69][] | `concat` | `concat` | | | +| ✅ [#70][] | `contains` | `contains` | | | +| ✅ [#82][] | `delay` | `delay` | | | +| | `distinct` | `distinct` | | | +| | `distinctBy` | `dictinctBy` | `distinctByAsync` | | +| ✅ [#2][] | `empty` | `empty` | | | +| ✅ [#23][] | `exactlyOne` | `exactlyOne` | | | +| ✅ [#83][] | `except` | `except` | | | +| ✅ [#83][] | | `exceptOfSeq` | | | +| ✅ [#70][] | `exists` | `exists` | `existsAsync` | | +| | `exists2` | `exists2` | | | +| ✅ [#23][] | `filter` | `filter` | `filterAsync` | | +| ✅ [#23][] | `find` | `find` | `findAsync` | | +| 🚫 | `findBack` | | | [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.") | +| ✅ [#68][] | `findIndex` | `findIndex` | `findIndexAsync` | | +| 🚫 | `findIndexBack` | n/a | n/a | [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.") | +| ✅ [#2][] | `fold` | `fold` | `foldAsync` | | +| | `fold2` | `fold2` | `fold2Async` | | +| 🚫 | `foldBack` | | | [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.") | +| 🚫 | `foldBack2` | | | [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.") | +| | `forall` | `forall` | `forallAsync` | | +| | `forall2` | `forall2` | `forall2Async` | | +| ❓ | `groupBy` | `groupBy` | `groupByAsync` | [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.") | +| ✅ [#23][] | `head` | `head` | | | +| ✅ [#68][] | `indexed` | `indexed` | | | +| ✅ [#69][] | `init` | `init` | `initAsync` | | +| ✅ [#69][] | `initInfinite` | `initInfinite` | `initInfiniteAsync` | | +| | `insertAt` | `insertAt` | | | +| | `insertManyAt` | `insertManyAt` | | | +| ✅ [#23][] | `isEmpty` | `isEmpty` | | | +| ✅ [#23][] | `item` | `item` | | | +| ✅ [#2][] | `iter` | `iter` | `iterAsync` | | +| | `iter2` | `iter2` | `iter2Async` | | +| ✅ [#2][] | `iteri` | `iteri` | `iteriAsync` | | +| | `iteri2` | `iteri2` | `iteri2Async` | | +| ✅ [#23][] | `last` | `last` | | | +| ✅ [#53][] | `length` | `length` | | | +| ✅ [#53][] | | `lengthBy` | `lengthByAsync` | | +| ✅ [#2][] | `map` | `map` | `mapAsync` | | +| | `map2` | `map2` | `map2Async` | | +| | `map3` | `map3` | `map3Async` | | +| | `mapFold` | `mapFold` | `mapFoldAsync` | | +| 🚫 | `mapFoldBack` | | | [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.") | +| ✅ [#2][] | `mapi` | `mapi` | `mapiAsync` | | +| | `mapi2` | `mapi2` | `mapi2Async` | | +| | `max` | `max` | | | +| | `maxBy` | `maxBy` | `maxByAsync` | | +| | `min` | `min` | | | +| | `minBy` | `minBy` | `minByAsync` | | +| ✅ [#2][] | `ofArray` | `ofArray` | | | +| ✅ [#2][] | | `ofAsyncArray` | | | +| ✅ [#2][] | | `ofAsyncList` | | | +| ✅ [#2][] | | `ofAsyncSeq` | | | +| ✅ [#2][] | `ofList` | `ofList` | | | +| ✅ [#2][] | | `ofTaskList` | | | +| ✅ [#2][] | | `ofResizeArray` | | | +| ✅ [#2][] | | `ofSeq` | | | +| ✅ [#2][] | | `ofTaskArray` | | | +| ✅ [#2][] | | `ofTaskList` | | | +| ✅ [#2][] | | `ofTaskSeq` | | | +| | `pairwise` | `pairwise` | | | +| | `permute` | `permute` | `permuteAsync` | | +| ✅ [#23][] | `pick` | `pick` | `pickAsync` | | +| 🚫 | `readOnly` | | | [note #3](#note3 "The motivation for 'readOnly' in 'Seq' is that a cast from a mutable array or list to a 'seq<_>' is valid and can be cast back, leading to a mutable sequence. Since 'TaskSeq' doesn't implement 'IEnumerable<_>', such casts are not possible.") | +| | `reduce` | `reduce` | `reduceAsync` | | +| 🚫 | `reduceBack` | | | [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.") | +| | `removeAt` | `removeAt` | | | +| | `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.") | +| | `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` | | | +| ✅ [#209][]| | `drop` | | | +| | `skipWhile` | `skipWhile` | `skipWhileAsync` | | +| | | `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.") | +| | `splitInto` | `splitInto` | | | +| | `sum` | `sum` | | | +| | `sumBy` | `sumBy` | `sumByAsync` | | +| ✅ [#76][] | `tail` | `tail` | | | +| ✅ [#209][]| `take` | `take` | | | +| ✅ [#126][]| `takeWhile` | `takeWhile` | `takeWhileAsync` | | +| ✅ [#126][]| | `takeWhileInclusive` | `takeWhileInclusiveAsync` | | +| ✅ [#2][] | `toArray` | `toArray` | `toArrayAsync` | | +| ✅ [#2][] | | `toIList` | `toIListAsync` | | +| ✅ [#2][] | `toList` | `toList` | `toListAsync` | | +| ✅ [#2][] | | `toResizeArray` | `toResizeArrayAsync` | | +| ✅ [#2][] | | `toSeq` | `toSeqAsync` | | +| | | […] | | | +| ❓ | `transpose` | | | [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.") | +| ✅ [#209][]| `truncate` | `truncate` | | | +| ✅ [#23][] | `tryExactlyOne` | `tryExactlyOne` | `tryExactlyOneAsync` | | +| ✅ [#23][] | `tryFind` | `tryFind` | `tryFindAsync` | | +| 🚫 | `tryFindBack` | | | [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.") | +| ✅ [#68][] | `tryFindIndex` | `tryFindIndex` | `tryFindIndexAsync` | | +| 🚫 | `tryFindIndexBack` | | | [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.") | +| ✅ [#23][] | `tryHead` | `tryHead` | | | +| ✅ [#23][] | `tryItem` | `tryItem` | | | +| ✅ [#23][] | `tryLast` | `tryLast` | | | +| ✅ [#23][] | `tryPick` | `tryPick` | `tryPickAsync` | | +| ✅ [#76][] | | `tryTail` | | | +| | `unfold` | `unfold` | `unfoldAsync` | | +| | `updateAt` | `updateAt` | | | +| | `where` | `where` | `whereAsync` | | +| | `windowed` | `windowed` | | | +| ✅ [#2][] | `zip` | `zip` | | | +| | `zip3` | `zip3` | | | +| | | `zip4` | | | + + +¹⁾ _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._ +²⁾ _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 `xxxBack` iterators._ ³⁾ _The motivation for `readOnly` in `Seq` is that a cast from a mutable array or list to a `seq<_>` is valid and can be cast back, leading to a mutable sequence. Since `TaskSeq` doesn't implement `IEnumerable<_>`, such casts are not possible._ ## More information @@ -476,6 +475,7 @@ module TaskSeq = val concat: sources: TaskSeq<#TaskSeq<'T>> -> TaskSeq<'T> val contains<'T when 'T: equality> : value: 'T -> source: TaskSeq<'T> -> Task val delay: generator: (unit -> TaskSeq<'T>) -> TaskSeq<'T> + val drop: count: int -> source: TaskSeq<'T> -> TaskSeq<'T> val empty<'T> : TaskSeq<'T> val exactlyOne: source: TaskSeq<'T> -> Task<'T> val except<'T when 'T: equality> : itemsToExclude: TaskSeq<'T> -> source: TaskSeq<'T> -> TaskSeq<'T> @@ -525,7 +525,9 @@ module TaskSeq = val pickAsync: chooser: ('T -> #Task<'U option>) -> source: TaskSeq<'T> -> Task<'U> val prependSeq: source1: seq<'T> -> source2: TaskSeq<'T> -> TaskSeq<'T> val singleton: source: 'T -> TaskSeq<'T> + val skip: count: int -> source: TaskSeq<'T> -> TaskSeq<'T> val tail: source: TaskSeq<'T> -> Task> + val take: count: int -> source: TaskSeq<'T> -> TaskSeq<'T> val takeWhile: predicate: ('T -> bool) -> source: TaskSeq<'T> -> Task> val takeWhileAsync: predicate: ('T -> #Task) -> source: TaskSeq<'T> -> Task> val takeWhileInclusive: predicate: ('T -> bool) -> source: TaskSeq<'T> -> Task> @@ -537,6 +539,7 @@ module TaskSeq = val toListAsync: source: TaskSeq<'T> -> Task<'T list> val toResizeArrayAsync: source: TaskSeq<'T> -> Task> val toSeq: source: TaskSeq<'T> -> seq<'T> + val truncate: count: int -> source: TaskSeq<'T> -> TaskSeq<'T> val tryExactlyOne: source: TaskSeq<'T> -> Task<'T option> val tryFind: predicate: ('T -> bool) -> source: TaskSeq<'T> -> Task<'T option> val tryFindAsync: predicate: ('T -> #Task) -> source: TaskSeq<'T> -> Task<'T option> @@ -596,6 +599,7 @@ module TaskSeq = [#90]: https://github.com/fsprojects/FSharp.Control.TaskSeq/pull/90 [#126]: https://github.com/fsprojects/FSharp.Control.TaskSeq/pull/126 [#133]: https://github.com/fsprojects/FSharp.Control.TaskSeq/issues/133 +[#209]: https://github.com/fsprojects/FSharp.Control.TaskSeq/issues/209 [issues]: https://github.com/fsprojects/FSharp.Control.TaskSeq/issues [nuget]: https://www.nuget.org/packages/FSharp.Control.TaskSeq/ From 23bb2482008b70158f2ac286dfdfd51d4221bcd0 Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Thu, 21 Dec 2023 00:33:45 +0100 Subject: [PATCH 5/5] Also update the nuget readme, which differs significantly --- assets/nuget-package-readme.md | 287 +++++++++++++++++---------------- 1 file changed, 149 insertions(+), 138 deletions(-) diff --git a/assets/nuget-package-readme.md b/assets/nuget-package-readme.md index 35e6320c..440af182 100644 --- a/assets/nuget-package-readme.md +++ b/assets/nuget-package-readme.md @@ -1,6 +1,9 @@ # TaskSeq -An implementation of [`IAsyncEnumerable<'T>`][3] as a computation expression: `taskSeq { ... }` with an accompanying `TaskSeq` module. +An implementation of [`IAsyncEnumerable<'T>`][3] as a computation expression: `taskSeq { ... }` with an accompanying `TaskSeq` module, that allows seamless use of asynchronous sequences similar to F#'s native `seq` and `task` CE's. + +This readme covers the highlights and a summary of implemented functions. +A more extensive overview can be found in the [repository's readme][1]. ----------------------------------------- @@ -56,7 +59,7 @@ let helloTs = taskSeq { yield "Hello, World!" } let f() = task { // using toList forces execution of whole sequence let! hello = TaskSeq.toList helloTs // toList returns a Task<'T list> - return List.head hello + return List.head hello } // can be mixed with normal sequences @@ -76,15 +79,15 @@ let allFilesAsLines() = taskSeq { yield! contents } -let write file = +let write file = allFilesAsLines() - + // synchronous map function on asynchronous task sequence |> TaskSeq.map (fun x -> x.Replace("a", "b")) // asynchronous map |> TaskSeq.mapAsync (fun x -> task { return "hello: " + x }) - + // asynchronous iter |> TaskSeq.iterAsync (fun data -> File.WriteAllTextAsync(fileName, data)) @@ -102,144 +105,149 @@ let feedFromTwitter user pwd = taskSeq { We are working hard on getting a full set of module functions on `TaskSeq` that can be used with `IAsyncEnumerable` sequences. Our guide is the set of F# `Seq` functions in F# Core and, where applicable, the functions provided from `AsyncSeq`. Each implemented function is documented through XML doc comments to provide the necessary context-sensitive help. -The following is the progress report: - -| Done | `Seq` | `TaskSeq` | Variants | Remarks | -|------------------|--------------------|-----------------|----------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| ❓ | `allPairs` | `allPairs` | | [note #1](#note-1 "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.") | -| ✅ [#81][] | `append` | `append` | | | -| ✅ [#81][] | | | `appendSeq` | | -| ✅ [#81][] | | | `prependSeq` | | -| | `average` | `average` | | | -| | `averageBy` | `averageBy` | `averageByAsync` | | -| ❓ | `cache` | `cache` | | [note #1](#note-1 "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.") | -| ✅ [#67][] | `cast` | `cast` | | | -| ✅ [#67][] | | | `box` | | -| ✅ [#67][] | | | `unbox` | | -| ✅ [#23][] | `choose` | `choose` | `chooseAsync` | | -| | `chunkBySize` | `chunkBySize` | | | -| ✅ [#11][] | `collect` | `collect` | `collectAsync` | | -| ✅ [#11][] | | `collectSeq` | `collectSeqAsync` | | -| | `compareWith` | `compareWith` | `compareWithAsync` | | -| ✅ [#69][] | `concat` | `concat` | | | -| ✅ [#70][] | `contains` | `contains` | | | -| ✅ [#82][] | `delay` | `delay` | | | -| | `distinct` | `distinct` | | | -| | `distinctBy` | `dictinctBy` | `distinctByAsync` | | -| ✅ [#2][] | `empty` | `empty` | | | -| ✅ [#23][] | `exactlyOne` | `exactlyOne` | | | -| ✅ [#83][] | `except` | `except` | | | -| ✅ [#83][] | | `exceptOfSeq` | | | -| ✅ [#70][] | `exists` | `exists` | `existsAsync` | | -| | `exists2` | `exists2` | | | -| ✅ [#23][] | `filter` | `filter` | `filterAsync` | | -| ✅ [#23][] | `find` | `find` | `findAsync` | | -| 🚫 | `findBack` | | | [note #2](#note-2 "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.") | -| ✅ [#68][] | `findIndex` | `findIndex` | `findIndexAsync` | | -| 🚫 | `findIndexBack` | n/a | n/a | [note #2](#note-2 "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.") | -| ✅ [#2][] | `fold` | `fold` | `foldAsync` | | -| | `fold2` | `fold2` | `fold2Async` | | -| 🚫 | `foldBack` | | | [note #2](#note-2 "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.") | -| 🚫 | `foldBack2` | | | [note #2](#note-2 "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.") | -| | `forall` | `forall` | `forallAsync` | | -| | `forall2` | `forall2` | `forall2Async` | | -| ❓ | `groupBy` | `groupBy` | `groupByAsync` | [note #1](#note-1 "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.") | -| ✅ [#23][] | `head` | `head` | | | -| ✅ [#68][] | `indexed` | `indexed` | | | -| ✅ [#69][] | `init` | `init` | `initAsync` | | -| ✅ [#69][] | `initInfinite` | `initInfinite` | `initInfiniteAsync` | | -| | `insertAt` | `insertAt` | | | -| | `insertManyAt` | `insertManyAt` | | | -| ✅ [#23][] | `isEmpty` | `isEmpty` | | | -| ✅ [#23][] | `item` | `item` | | | -| ✅ [#2][] | `iter` | `iter` | `iterAsync` | | -| | `iter2` | `iter2` | `iter2Async` | | -| ✅ [#2][] | `iteri` | `iteri` | `iteriAsync` | | -| | `iteri2` | `iteri2` | `iteri2Async` | | -| ✅ [#23][] | `last` | `last` | | | -| ✅ [#53][] | `length` | `length` | | | -| ✅ [#53][] | | `lengthBy` | `lengthByAsync` | | -| ✅ [#2][] | `map` | `map` | `mapAsync` | | -| | `map2` | `map2` | `map2Async` | | -| | `map3` | `map3` | `map3Async` | | -| | `mapFold` | `mapFold` | `mapFoldAsync` | | -| 🚫 | `mapFoldBack` | | | [note #2](#note-2 "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.") | -| ✅ [#2][] | `mapi` | `mapi` | `mapiAsync` | | -| | `mapi2` | `mapi2` | `mapi2Async` | | -| | `max` | `max` | | | -| | `maxBy` | `maxBy` | `maxByAsync` | | -| | `min` | `min` | | | -| | `minBy` | `minBy` | `minByAsync` | | -| ✅ [#2][] | `ofArray` | `ofArray` | | | -| ✅ [#2][] | | `ofAsyncArray` | | | -| ✅ [#2][] | | `ofAsyncList` | | | -| ✅ [#2][] | | `ofAsyncSeq` | | | -| ✅ [#2][] | `ofList` | `ofList` | | | -| ✅ [#2][] | | `ofTaskList` | | | -| ✅ [#2][] | | `ofResizeArray` | | | -| ✅ [#2][] | | `ofSeq` | | | -| ✅ [#2][] | | `ofTaskArray` | | | -| ✅ [#2][] | | `ofTaskList` | | | -| ✅ [#2][] | | `ofTaskSeq` | | | -| | `pairwise` | `pairwise` | | | -| | `permute` | `permute` | `permuteAsync` | | -| ✅ [#23][] | `pick` | `pick` | `pickAsync` | | -| 🚫 | `readOnly` | | | [note #3](#note-3 "The motivation for 'readOnly' in 'Seq' is that a cast from a mutable array or list to a 'seq<_>' is valid and can be cast back, leading to a mutable sequence. Since 'TaskSeq' doesn't implement 'IEnumerable<_>', such casts are not possible.") | -| | `reduce` | `reduce` | `reduceAsync` | | -| 🚫 | `reduceBack` | | | [note #2](#note-2 "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.") | -| | `removeAt` | `removeAt` | | | -| | `removeManyAt` | `removeManyAt` | | | -| | `replicate` | `replicate` | | | -| ❓ | `rev` | | | [note #1](#note-1 "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.") | -| | `scan` | `scan` | `scanAsync` | | -| 🚫 | `scanBack` | | | [note #2](#note-2 "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` | | | -| | `skip` | `skip` | | | -| | `skipWhile` | `skipWhile` | `skipWhileAsync` | | -| ❓ | `sort` | | | [note #1](#note-1 "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](#note-1 "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](#note-1 "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](#note-1 "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](#note-1 "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.") | -| | `splitInto` | `splitInto` | | | -| | `sum` | `sum` | | | -| | `sumBy` | `sumBy` | `sumByAsync` | | -| ✅ [#76][] | `tail` | `tail` | | | -| | `take` | `take` | | | -| | `takeWhile` | `takeWhile` | `takeWhileAsync` | | -| ✅ [#2][] | `toArray` | `toArray` | `toArrayAsync` | | -| ✅ [#2][] | | `toIList` | `toIListAsync` | | -| ✅ [#2][] | `toList` | `toList` | `toListAsync` | | -| ✅ [#2][] | | `toResizeArray` | `toResizeArrayAsync` | | -| ✅ [#2][] | | `toSeq` | `toSeqAsync` | | -| | | […] | | | -| ❓ | `transpose` | | | [note #1](#note-1 "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.") | -| | `truncate` | `truncate` | | | -| ✅ [#23][] | `tryExactlyOne` | `tryExactlyOne` | `tryExactlyOneAsync` | | -| ✅ [#23][] | `tryFind` | `tryFind` | `tryFindAsync` | | -| 🚫 | `tryFindBack` | | | [note #2](#note-2 "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.") | -| ✅ [#68][] | `tryFindIndex` | `tryFindIndex` | `tryFindIndexAsync` | | -| 🚫 | `tryFindIndexBack` | | | [note #2](#note-2 "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.") | -| ✅ [#23][] | `tryHead` | `tryHead` | | | -| ✅ [#23][] | `tryItem` | `tryItem` | | | -| ✅ [#23][] | `tryLast` | `tryLast` | | | -| ✅ [#23][] | `tryPick` | `tryPick` | `tryPickAsync` | | -| ✅ [#76][] | | `tryTail` | | | -| | `unfold` | `unfold` | `unfoldAsync` | | -| | `updateAt` | `updateAt` | | | -| | `where` | `where` | `whereAsync` | | -| | `windowed` | `windowed` | | | -| ✅ [#2][] | `zip` | `zip` | | | -| | `zip3` | `zip3` | | | -| | | `zip4` | | | +We are working hard on getting a full set of module functions on `TaskSeq` that can be used with `IAsyncEnumerable` sequences. Our guide is the set of F# `Seq` functions in F# Core and, where applicable, the functions provided by `AsyncSeq`. Each implemented function is documented through XML doc comments to provide the necessary context-sensitive help. + +This is what was implemented, planned or skipped: + +| Done | `Seq` | `TaskSeq` | Variants | Remarks | +|------------------|--------------------|----------------------|---------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| ❓ | `allPairs` | `allPairs` | | [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.") | +| ✅ [#81][] | `append` | `append` | | | +| ✅ [#81][] | | | `appendSeq` | | +| ✅ [#81][] | | | `prependSeq` | | +| | `average` | `average` | | | +| | `averageBy` | `averageBy` | `averageByAsync` | | +| ❓ | `cache` | `cache` | | [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.") | +| ✅ [#67][] | `cast` | `cast` | | | +| ✅ [#67][] | | | `box` | | +| ✅ [#67][] | | | `unbox` | | +| ✅ [#23][] | `choose` | `choose` | `chooseAsync` | | +| | `chunkBySize` | `chunkBySize` | | | +| ✅ [#11][] | `collect` | `collect` | `collectAsync` | | +| ✅ [#11][] | | `collectSeq` | `collectSeqAsync` | | +| | `compareWith` | `compareWith` | `compareWithAsync` | | +| ✅ [#69][] | `concat` | `concat` | | | +| ✅ [#70][] | `contains` | `contains` | | | +| ✅ [#82][] | `delay` | `delay` | | | +| | `distinct` | `distinct` | | | +| | `distinctBy` | `dictinctBy` | `distinctByAsync` | | +| ✅ [#2][] | `empty` | `empty` | | | +| ✅ [#23][] | `exactlyOne` | `exactlyOne` | | | +| ✅ [#83][] | `except` | `except` | | | +| ✅ [#83][] | | `exceptOfSeq` | | | +| ✅ [#70][] | `exists` | `exists` | `existsAsync` | | +| | `exists2` | `exists2` | | | +| ✅ [#23][] | `filter` | `filter` | `filterAsync` | | +| ✅ [#23][] | `find` | `find` | `findAsync` | | +| 🚫 | `findBack` | | | [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.") | +| ✅ [#68][] | `findIndex` | `findIndex` | `findIndexAsync` | | +| 🚫 | `findIndexBack` | n/a | n/a | [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.") | +| ✅ [#2][] | `fold` | `fold` | `foldAsync` | | +| | `fold2` | `fold2` | `fold2Async` | | +| 🚫 | `foldBack` | | | [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.") | +| 🚫 | `foldBack2` | | | [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.") | +| | `forall` | `forall` | `forallAsync` | | +| | `forall2` | `forall2` | `forall2Async` | | +| ❓ | `groupBy` | `groupBy` | `groupByAsync` | [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.") | +| ✅ [#23][] | `head` | `head` | | | +| ✅ [#68][] | `indexed` | `indexed` | | | +| ✅ [#69][] | `init` | `init` | `initAsync` | | +| ✅ [#69][] | `initInfinite` | `initInfinite` | `initInfiniteAsync` | | +| | `insertAt` | `insertAt` | | | +| | `insertManyAt` | `insertManyAt` | | | +| ✅ [#23][] | `isEmpty` | `isEmpty` | | | +| ✅ [#23][] | `item` | `item` | | | +| ✅ [#2][] | `iter` | `iter` | `iterAsync` | | +| | `iter2` | `iter2` | `iter2Async` | | +| ✅ [#2][] | `iteri` | `iteri` | `iteriAsync` | | +| | `iteri2` | `iteri2` | `iteri2Async` | | +| ✅ [#23][] | `last` | `last` | | | +| ✅ [#53][] | `length` | `length` | | | +| ✅ [#53][] | | `lengthBy` | `lengthByAsync` | | +| ✅ [#2][] | `map` | `map` | `mapAsync` | | +| | `map2` | `map2` | `map2Async` | | +| | `map3` | `map3` | `map3Async` | | +| | `mapFold` | `mapFold` | `mapFoldAsync` | | +| 🚫 | `mapFoldBack` | | | [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.") | +| ✅ [#2][] | `mapi` | `mapi` | `mapiAsync` | | +| | `mapi2` | `mapi2` | `mapi2Async` | | +| | `max` | `max` | | | +| | `maxBy` | `maxBy` | `maxByAsync` | | +| | `min` | `min` | | | +| | `minBy` | `minBy` | `minByAsync` | | +| ✅ [#2][] | `ofArray` | `ofArray` | | | +| ✅ [#2][] | | `ofAsyncArray` | | | +| ✅ [#2][] | | `ofAsyncList` | | | +| ✅ [#2][] | | `ofAsyncSeq` | | | +| ✅ [#2][] | `ofList` | `ofList` | | | +| ✅ [#2][] | | `ofTaskList` | | | +| ✅ [#2][] | | `ofResizeArray` | | | +| ✅ [#2][] | | `ofSeq` | | | +| ✅ [#2][] | | `ofTaskArray` | | | +| ✅ [#2][] | | `ofTaskList` | | | +| ✅ [#2][] | | `ofTaskSeq` | | | +| | `pairwise` | `pairwise` | | | +| | `permute` | `permute` | `permuteAsync` | | +| ✅ [#23][] | `pick` | `pick` | `pickAsync` | | +| 🚫 | `readOnly` | | | [note #3](#note3 "The motivation for 'readOnly' in 'Seq' is that a cast from a mutable array or list to a 'seq<_>' is valid and can be cast back, leading to a mutable sequence. Since 'TaskSeq' doesn't implement 'IEnumerable<_>', such casts are not possible.") | +| | `reduce` | `reduce` | `reduceAsync` | | +| 🚫 | `reduceBack` | | | [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.") | +| | `removeAt` | `removeAt` | | | +| | `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.") | +| | `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` | | | +| ✅ [#209][]| | `drop` | | | +| | `skipWhile` | `skipWhile` | `skipWhileAsync` | | +| | | `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.") | +| | `splitInto` | `splitInto` | | | +| | `sum` | `sum` | | | +| | `sumBy` | `sumBy` | `sumByAsync` | | +| ✅ [#76][] | `tail` | `tail` | | | +| ✅ [#209][]| `take` | `take` | | | +| ✅ [#126][]| `takeWhile` | `takeWhile` | `takeWhileAsync` | | +| ✅ [#126][]| | `takeWhileInclusive` | `takeWhileInclusiveAsync` | | +| ✅ [#2][] | `toArray` | `toArray` | `toArrayAsync` | | +| ✅ [#2][] | | `toIList` | `toIListAsync` | | +| ✅ [#2][] | `toList` | `toList` | `toListAsync` | | +| ✅ [#2][] | | `toResizeArray` | `toResizeArrayAsync` | | +| ✅ [#2][] | | `toSeq` | `toSeqAsync` | | +| | | […] | | | +| ❓ | `transpose` | | | [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.") | +| ✅ [#209][]| `truncate` | `truncate` | | | +| ✅ [#23][] | `tryExactlyOne` | `tryExactlyOne` | `tryExactlyOneAsync` | | +| ✅ [#23][] | `tryFind` | `tryFind` | `tryFindAsync` | | +| 🚫 | `tryFindBack` | | | [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.") | +| ✅ [#68][] | `tryFindIndex` | `tryFindIndex` | `tryFindIndexAsync` | | +| 🚫 | `tryFindIndexBack` | | | [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.") | +| ✅ [#23][] | `tryHead` | `tryHead` | | | +| ✅ [#23][] | `tryItem` | `tryItem` | | | +| ✅ [#23][] | `tryLast` | `tryLast` | | | +| ✅ [#23][] | `tryPick` | `tryPick` | `tryPickAsync` | | +| ✅ [#76][] | | `tryTail` | | | +| | `unfold` | `unfold` | `unfoldAsync` | | +| | `updateAt` | `updateAt` | | | +| | `where` | `where` | `whereAsync` | | +| | `windowed` | `windowed` | | | +| ✅ [#2][] | `zip` | `zip` | | | +| | `zip3` | `zip3` | | | +| | | `zip4` | | | #### Note 1 -_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._ +_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._ #### Note 2 -_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 `xxxBack` iterators._ +_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 `xxxBack` iterators._ #### Note 3 @@ -267,6 +275,7 @@ _The motivation for `readOnly` in `Seq` is that a cast from a mutable array or l - [Docs on MSDN][18] form a good summary and starting point. - Arguably the best [step-by-step tutorial to using and building computation expressions][19] by Scott Wlaschin. +[1]: https://github.com/fsprojects/FSharp.Control.TaskSeq#taskseq [3]: https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.iasyncenumerable-1?view=net-7.0 [4]: https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.iasyncenumerator-1.movenextasync?view=net-7.0 [5]: https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.iasyncenumerator-1?view=net-7.0 @@ -297,4 +306,6 @@ _The motivation for `readOnly` in `Seq` is that a cast from a mutable array or l [#81]: https://github.com/fsprojects/FSharp.Control.TaskSeq/pull/81 [#82]: https://github.com/fsprojects/FSharp.Control.TaskSeq/pull/82 [#83]: https://github.com/fsprojects/FSharp.Control.TaskSeq/pull/83 -[#90]: https://github.com/fsprojects/FSharp.Control.TaskSeq/pull/90 \ No newline at end of file +[#90]: https://github.com/fsprojects/FSharp.Control.TaskSeq/pull/90 +[#126]: https://github.com/fsprojects/FSharp.Control.TaskSeq/pull/126 +[#209]: https://github.com/fsprojects/FSharp.Control.TaskSeq/issues/209 \ No newline at end of file