diff --git a/README.md b/README.md index c932efa2..a102b443 100644 --- a/README.md +++ b/README.md @@ -64,130 +64,134 @@ We are working hard on getting a full set of module functions on `TaskSeq` that The following is the progress report: -| Done | `Seq` | `TaskSeq` | `async` variant | 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.") | -| | `append` | `append` | | | -| | `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.") | -| ✅ | `cast` | `cast` | | | -| ✅ | | `box` | | | -| ✅ | | `unbox` | | | -| ✅ | `choose` | `choose` | `chooseAsync` | | -| | `chunkBySize` | `chunkBySize` | | | -| ✅ | `collect` | `collect` | `collectAsync` | | -| | `compareWith` | `compareWith` | `compareWithAsync` | | -| ✅ | `concat` | `concat` | | | -| ✅ | `contains` | `contains` | | | -| | `delay` | `delay` | | | -| | `distinct` | `distinct` | | | -| | `distinctBy` | `dictinctBy` | `distinctByAsync` | | -| ✅ | `empty` | `empty` | | | -| ✅ | `exactlyOne` | `exactlyOne` | | | -| | `except` | `except` | | | -| ✅ | `exists` | `exists` | | | -| | `exists2` | `exists2` | | | -| ✅ | `filter` | `filter` | `filterAsync` | | -| ✅ | `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.") | -| ✅ | `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.") | -| ✅ | `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.") | -| ✅ | `head` | `head` | | | -| ✅ | `indexed` | `indexed` | | | -| ✅ | `init` | `init` | `initAsync` | | -| ✅ | `initInfinite` | `initInfinite` | `initInfiniteAsync` | | -| | `insertAt` | `insertAt` | | | -| | `insertManyAt` | `insertManyAt` | | | -| ✅ | `isEmpty` | `isEmpty` | | | -| ✅ | `item` | `item` | | | -| ✅ | `iter` | `iter` | `iterAsync` | | -| | `iter2` | `iter2` | `iter2Async` | | -| ✅ | `iteri` | `iteri` | `iteriAsync` | | -| | `iteri2` | `iteri2` | `iteri2Async` | | -| ✅ | `last` | `last` | | | -| ✅ | `length` | `length` | | | -| ✅ | | `lengthBy` | `lengthByAsyn` | | -| ✅ | `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.") | -| ✅ | `mapi` | `mapi` | `mapiAsync` | | -| | `mapi2` | `mapi2` | `mapi2Async` | | -| | `max` | `max` | | | -| | `maxBy` | `maxBy` | `maxByAsync` | | -| | `min` | `min` | | | -| | `minBy` | `minBy` | `minByAsync` | | -| ✅ | `ofArray` | `ofArray` | | | -| ✅ | | `ofAsyncArray` | | | -| ✅ | | `ofAsyncList` | | | -| ✅ | | `ofAsyncSeq` | | | -| ✅ | `ofList` | `ofList` | | | -| ✅ | | `ofTaskList` | | | -| ✅ | | `ofResizeArray` | | | -| ✅ | | `ofSeq` | | | -| ✅ | | `ofTaskArray` | | | -| ✅ | | `ofTaskList` | | | -| ✅ | | `ofTaskSeq` | | | -| | `pairwise` | `pairwise` | | | -| | `permute` | `permute` | `permuteAsync` | | -| ✅ | `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.") | -| | `singleton` | `singleton` | | | -| | `skip` | `skip` | | | -| | `skipWhile` | `skipWhile` | `skipWhileAsync` | | -| ❓ | `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` | | -| ✅ | `tail` | `tail` | | | -| | `take` | `take` | | | -| | `takeWhile` | `takeWhile` | `takeWhileAsync` | | -| ✅ | `toArray` | `toArray` | `toArrayAsync` | | -| ✅ | | `toIList` | `toIListAsync` | | -| ✅ | `toList` | `toList` | `toListAsync` | | -| ✅ | | `toResizeArray` | `toResizeArrayAsync` | | -| ✅ | | `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` | | | -| ✅ | `tryExactlyOne` | `tryExactlyOne` | `tryExactlyOneAsync` | | -| ✅ | `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.") | -| ✅ | `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.") | -| ✅ | `tryHead` | `tryHead` | | | -| ✅ | `tryItem` | `tryItem` | | | -| ✅ | `tryLast` | `tryLast` | | | -| ✅ | `tryPick` | `tryPick` | `tryPickAsync` | | -| ✅ | | `tryTail` | | | -| | `unfold` | `unfold` | `unfoldAsync` | | -| | `updateAt` | `updateAt` | | | -| | `where` | `where` | `whereAsync` | | -| | `windowed` | `windowed` | | | -| ✅ | `zip` | `zip` | | | -| | `zip3` | `zip3` | | | -| | | `zip4` | +| 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` | | | +| | `delay` | `delay` | | | +| | `distinct` | `distinct` | | | +| | `distinctBy` | `dictinctBy` | `distinctByAsync` | | +| ✅ [#2][] | `empty` | `empty` | | | +| ✅ [#23][] | `exactlyOne` | `exactlyOne` | | | +| | `except` | `except` | | | +| ✅ [#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.") | +| | `singleton` | `singleton` | | | +| | `skip` | `skip` | | | +| | `skipWhile` | `skipWhile` | `skipWhileAsync` | | +| ❓ | `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` | | | +| | `takeWhile` | `takeWhile` | `takeWhileAsync` | | +| ✅ [#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._ @@ -595,3 +599,15 @@ module TaskSeq = [18]: https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/computation-expressions [19]: https://fsharpforfunandprofit.com/series/computation-expressions/ [20]: https://github.com/dotnet/fsharp/blob/d5312aae8aad650f0043f055bb14c3aa8117e12e/tests/benchmarks/CompiledCodeBenchmarks/TaskPerf/TaskPerf/taskSeq.fs + +[#2]: https://github.com/fsprojects/FSharp.Control.TaskSeq/pull/2 +[#11]: https://github.com/fsprojects/FSharp.Control.TaskSeq/pull/11 +[#23]: https://github.com/fsprojects/FSharp.Control.TaskSeq/pull/23 +[#53]: https://github.com/fsprojects/FSharp.Control.TaskSeq/pull/53 +[#67]: https://github.com/fsprojects/FSharp.Control.TaskSeq/pull/67 +[#68]: https://github.com/fsprojects/FSharp.Control.TaskSeq/pull/68 +[#69]: https://github.com/fsprojects/FSharp.Control.TaskSeq/pull/69 +[#70]: https://github.com/fsprojects/FSharp.Control.TaskSeq/pull/70 +[#76]: https://github.com/fsprojects/FSharp.Control.TaskSeq/pull/76 +[#81]: https://github.com/fsprojects/FSharp.Control.TaskSeq/pull/81 + 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 e18b3a0e..4ab3edd3 100644 --- a/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj +++ b/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj @@ -12,6 +12,7 @@ + diff --git a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Append.Tests.fs b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Append.Tests.fs new file mode 100644 index 00000000..0448acc7 --- /dev/null +++ b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Append.Tests.fs @@ -0,0 +1,115 @@ +module TaskSeq.Tests.Append + +open System + +open Xunit +open FsUnit.Xunit +open FsToolkit.ErrorHandling + +open FSharp.Control +open System.Collections.Generic + +// +// TaskSeq.append +// TaskSeq.appendSeq +// TaskSeq.prependSeq +// + +let validateSequence ts = + ts + |> TaskSeq.toSeqCachedAsync + |> Task.map (Seq.map string) + |> Task.map (String.concat "") + |> Task.map (should equal "1234567891012345678910") + +module EmptySeq = + [)>] + let ``TaskSeq-append both args empty`` variant = + Gen.getEmptyVariant variant + |> TaskSeq.append (Gen.getEmptyVariant variant) + |> verifyEmpty + + [)>] + let ``TaskSeq-appendSeq both args empty`` variant = + Seq.empty + |> TaskSeq.appendSeq (Gen.getEmptyVariant variant) + |> verifyEmpty + + [)>] + let ``TaskSeq-prependSeq both args empty`` variant = + Gen.getEmptyVariant variant + |> TaskSeq.prependSeq Seq.empty + |> verifyEmpty + +module Immutable = + [)>] + let ``TaskSeq-append`` variant = + Gen.getSeqImmutable variant + |> TaskSeq.append (Gen.getSeqImmutable variant) + |> validateSequence + + [)>] + let ``TaskSeq-appendSeq with a list`` variant = + [ 1..10 ] + |> TaskSeq.appendSeq (Gen.getSeqImmutable variant) + |> validateSequence + + [)>] + let ``TaskSeq-appendSeq with an array`` variant = + [| 1..10 |] + |> TaskSeq.appendSeq (Gen.getSeqImmutable variant) + |> validateSequence + + [)>] + let ``TaskSeq-prependSeq with a list`` variant = + Gen.getSeqImmutable variant + |> TaskSeq.prependSeq [ 1..10 ] + |> validateSequence + + [)>] + let ``TaskSeq-prependSeq with an array`` variant = + Gen.getSeqImmutable variant + |> TaskSeq.prependSeq [| 1..10 |] + |> validateSequence + +module SideEffects = + [)>] + let ``TaskSeq-append consumes whole sequence once incl after-effects`` variant = + let mutable i = 0 + + taskSeq { + i <- i + 1 + yield! [ 1..10 ] + i <- i + 1 + } + |> TaskSeq.append (Gen.getSeqImmutable variant) + |> validateSequence + |> Task.map (fun () -> i |> should equal 2) + + [)>] + let ``TaskSeq-appendSeq consumes whole sequence once incl after-effects`` variant = + let mutable i = 0 + + let ts = taskSeq { + i <- i + 1 + yield! [ 1..10 ] + i <- i + 1 + } + + [| 1..10 |] + |> TaskSeq.appendSeq ts + |> validateSequence + |> Task.map (fun () -> i |> should equal 2) + + [)>] + let ``TaskSeq-prependSeq consumes whole sequence once incl after-effects`` variant = + let mutable i = 0 + + taskSeq { + i <- i + 1 + yield! [ 1..10 ] + i <- i + 1 + } + |> TaskSeq.prependSeq [ 1..10 ] + |> validateSequence + |> Task.map (fun () -> i |> should equal 2) diff --git a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Concat.Tests.fs b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Concat.Tests.fs index 2b50a81d..df4c07e8 100644 --- a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Concat.Tests.fs +++ b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Concat.Tests.fs @@ -41,11 +41,69 @@ module EmptySeq = module Immutable = [)>] - let ``TaskSeq-concat with empty sequences`` variant = + let ``TaskSeq-concat with three sequences of sequences`` variant = + taskSeq { + yield Gen.getSeqImmutable variant // not yield-bang! + yield Gen.getSeqImmutable variant + yield Gen.getSeqImmutable variant + } + |> TaskSeq.concat + |> validateSequence + + [)>] + let ``TaskSeq-concat with three sequences of sequences and few empties`` variant = + taskSeq { + yield TaskSeq.empty + yield Gen.getSeqImmutable variant // not yield-bang! + yield TaskSeq.empty + yield TaskSeq.empty + yield Gen.getSeqImmutable variant + yield TaskSeq.empty + yield Gen.getSeqImmutable variant + yield TaskSeq.empty + yield TaskSeq.empty + yield TaskSeq.empty + yield TaskSeq.empty + } + |> TaskSeq.concat + |> validateSequence + +module SideEffect = + [)>] + let ``TaskSeq-concat consumes until the end, including side-effects`` variant = + let mutable i = 0 + + taskSeq { + yield Gen.getSeqImmutable variant // not yield-bang! + yield Gen.getSeqImmutable variant + + yield taskSeq { + yield! [ 1..10 ] + i <- i + 1 + } + } + |> TaskSeq.concat + |> validateSequence + |> Task.map (fun () -> i |> should equal 1) + + [)>] + let ``TaskSeq-concat consumes side effects in empty sequences`` variant = + let mutable i = 0 + taskSeq { + yield taskSeq { do i <- i + 1 } yield Gen.getSeqImmutable variant // not yield-bang! + yield TaskSeq.empty + yield taskSeq { do i <- i + 1 } yield Gen.getSeqImmutable variant + yield TaskSeq.empty yield Gen.getSeqImmutable variant + yield TaskSeq.empty + yield TaskSeq.empty + yield TaskSeq.empty + yield TaskSeq.empty + yield taskSeq { do i <- i + 1 } } |> TaskSeq.concat |> validateSequence + |> Task.map (fun () -> i |> should equal 3) diff --git a/src/FSharp.Control.TaskSeq/TaskSeq.fs b/src/FSharp.Control.TaskSeq/TaskSeq.fs index f66d7e94..a9ac3bd9 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeq.fs +++ b/src/FSharp.Control.TaskSeq/TaskSeq.fs @@ -169,6 +169,21 @@ module TaskSeq = yield! (ts :> taskSeq<'T>) } + let append (source1: #taskSeq<'T>) (source2: #taskSeq<'T>) = taskSeq { + yield! (source1 :> IAsyncEnumerable<'T>) + yield! (source2 :> IAsyncEnumerable<'T>) + } + + let appendSeq (source1: #taskSeq<'T>) (source2: #seq<'T>) = taskSeq { + yield! (source1 :> IAsyncEnumerable<'T>) + yield! (source2 :> seq<'T>) + } + + let prependSeq (source1: #seq<'T>) (source2: #taskSeq<'T>) = taskSeq { + yield! (source1 :> seq<'T>) + yield! (source2 :> IAsyncEnumerable<'T>) + } + // // iter/map/collect functions // @@ -222,6 +237,7 @@ module TaskSeq = | Some result -> return result | None -> return Internal.raiseEmptySeq () } + let tryItem index source = Internal.tryItem index source let item index source = task { diff --git a/src/FSharp.Control.TaskSeq/TaskSeq.fsi b/src/FSharp.Control.TaskSeq/TaskSeq.fsi index fce10970..8787e63e 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeq.fsi +++ b/src/FSharp.Control.TaskSeq/TaskSeq.fsi @@ -107,6 +107,39 @@ module TaskSeq = /// Thrown when the input sequence is null. val concat: sources: taskSeq<#taskSeq<'T>> -> taskSeq<'T> + /// + /// Concatenates task sequences and in order as a single + /// task sequence. + /// + /// + /// The first input task sequence. + /// The second input task sequence. + /// The resulting task sequence. + /// Thrown when either of the input sequences is null. + val append: source1: #taskSeq<'T> -> source2: #taskSeq<'T> -> taskSeq<'T> + + /// + /// Concatenates a task sequence with a non-async F# in + /// and returns a single task sequence. + /// + /// + /// The input task sequence. + /// The input F# sequence. + /// The resulting task sequence. + /// Thrown when either of the input sequences is null. + val appendSeq: source1: #taskSeq<'T> -> source2: #seq<'T> -> taskSeq<'T> + + /// + /// Concatenates a non-async F# in with a task sequence in + /// and returns a single task sequence. + /// + /// + /// The input F# sequence. + /// The input task sequence. + /// The resulting task sequence. + /// Thrown when either of the input sequences is null. + val prependSeq: source1: #seq<'T> -> source2: #taskSeq<'T> -> taskSeq<'T> + /// Returns taskSeq as an array. This function is blocking until the sequence is exhausted and will properly dispose of the resources. val toList: source: taskSeq<'T> -> 'T list