diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
index 4ac8d90b..19b688b1 100644
--- a/.github/workflows/build.yaml
+++ b/.github/workflows/build.yaml
@@ -18,23 +18,3 @@ jobs:
# build it, test it, pack it
- name: Run dotnet build (release)
run: ./build.cmd
-
- # deploy:
- # name: deploy
- # runs-on: ubuntu-latest
- # if: github.ref == 'refs/heads/main'
- # steps:
- # # checkout the code
- # - name: checkout-code
- # uses: actions/checkout@v3
- # with:
- # fetch-depth: 0
- # # setup dotnet based on global.json
- # - name: setup-dotnet
- # uses: actions/setup-dotnet@v3
- # # push it to nuget
- # - name: deploy
- # run: make cd
- # env:
- # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-
diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml
index 234f2b0b..02eb3c0d 100644
--- a/.github/workflows/main.yaml
+++ b/.github/workflows/main.yaml
@@ -45,23 +45,3 @@ jobs:
# this path glob pattern requires forward slashes!
path: ./src/FSharp.Control.TaskSeq.Test/TestResults/test-results-release.trx
reporter: dotnet-trx
-
- # deploy:
- # name: deploy
- # runs-on: ubuntu-latest
- # if: github.ref == 'refs/heads/main'
- # steps:
- # # checkout the code
- # - name: checkout-code
- # uses: actions/checkout@v3
- # with:
- # fetch-depth: 0
- # # setup dotnet based on global.json
- # - name: setup-dotnet
- # uses: actions/setup-dotnet@v3
- # # push it to nuget
- # - name: deploy
- # run: make cd
- # env:
- # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-
diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml
new file mode 100644
index 00000000..c97c3343
--- /dev/null
+++ b/.github/workflows/publish.yaml
@@ -0,0 +1,30 @@
+name: Pack & Publish Nuget
+
+on:
+ push:
+ branches:
+ - main
+
+jobs:
+ publish:
+ name: Publish nuget (if new version)
+ runs-on: windows-latest
+ steps:
+ # checkout the code
+ - name: checkout-code
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+ # setup dotnet based on global.json
+ - name: setup-dotnet
+ uses: actions/setup-dotnet@v3
+ # build it, test it, pack it, publish it
+ - name: Run dotnet build (release, for nuget)
+ run: ./build.cmd
+ - 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"
+ 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
diff --git a/.gitignore b/.gitignore
index f5910e87..72d94d66 100644
--- a/.gitignore
+++ b/.gitignore
@@ -349,3 +349,4 @@ MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
*.ncrunchproject
+nuget-api-key.txt
diff --git a/Directory.Build.props b/Directory.Build.props
new file mode 100644
index 00000000..dd5c7f4c
--- /dev/null
+++ b/Directory.Build.props
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 7da1e2fe..126493b2 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,10 @@
[![build][buildstatus_img]][buildstatus]
[![test][teststatus_img]][teststatus]
+[](https://www.nuget.org/packages/FSharp.Control.TaskSeq/)
# TaskSeq
-An implementation [`IAsyncEnumerable<'T>`][3] as a `taskSeq` CE for F# with accompanying `TaskSeq` module.
-
-The `IAsyncEnumerable` interface was added to .NET in `.NET Core 3.0` and is part of `.NET Standard 2.1`. The main use-case was for iterative asynchronous enumeration over some resource. For instance, an event stream or a REST API interface with pagination, where each page is a [`MoveNextAsync`][4] call on the [`IAsyncEnumerator<'T>`][5] given by a call to [`GetAsyncEnumerator()`][6]. It has been relatively challenging to work properly with this type and dealing with each step being asynchronous, and the enumerator implementing [`IAsyncDisposable`][7] as well, which requires careful handling.
+An implementation of [`IAsyncEnumerable<'T>`][3] as a computation expression: `taskSeq { ... }` with an accompanying `TaskSeq` module.
-----------------------------------------
@@ -18,10 +17,15 @@ The `IAsyncEnumerable` interface was added to .NET in `.NET Core 3.0` and is par
More info: https://marketplace.visualstudio.com/items?itemName=yzhang.markdown-all-in-one#table-of-contents
-->
-- [Feature planning](#feature-planning)
-- [Implementation progress](#implementation-progress)
- - [`taskSeq` CE](#taskseq-ce)
- - [`TaskSeq` module functions](#taskseq-module-functions)
+- [Overview](#overview)
+ - [Module functions](#module-functions)
+ - [`taskSeq` computation expressions](#taskseq-computation-expressions)
+ - [Installation](#installation)
+ - [Examples](#examples)
+- [Status & planning](#status--planning)
+ - [Implementation progress](#implementation-progress)
+ - [Progress `taskSeq` CE](#progress-taskseq-ce)
+ - [Progress and implemented `TaskSeq` module functions](#progress-and-implemented-taskseq-module-functions)
- [More information](#more-information)
- [Futher reading `IAsyncEnumerable`](#futher-reading-iasyncenumerable)
- [Futher reading on resumable state machines](#futher-reading-on-resumable-state-machines)
@@ -38,27 +42,134 @@ The `IAsyncEnumerable` interface was added to .NET in `.NET Core 3.0` and is par
-----------------------------------------
-## Feature planning
+## Overview
+
+The `IAsyncEnumerable` interface was added to .NET in `.NET Core 3.0` and is part of `.NET Standard 2.1`. The main use-case was for iterative asynchronous enumeration over some resource. For instance, an event stream or a REST API interface with pagination, asynchronous reading over a list of files and accumulating the results, where each action can be modeled as a [`MoveNextAsync`][4] call on the [`IAsyncEnumerator<'T>`][5] given by a call to [`GetAsyncEnumerator()`][6].
+
+Since the introduction of `task` in F# the call for a native implementation of _task sequences_ has grown, in particular because proper iterating over an `IAsyncEnumerable` has proven challenging, especially if one wants to avoid mutable variables. This library is an answer to that call and implements the same _resumable state machine_ approach with `taskSeq`.
+
+### Module functions
+
+As with `seq` and `Seq`, this library comes with a bunch of well-known collection functions, like `TaskSeq.empty`, `isEmpty` or `TaskSeq.map`, `iter`, `collect`, `fold` and `TaskSeq.find`, `pick`, `choose`, `filter`. Where applicable, these come with async variants, like `TaskSeq.mapAsync` `iterAsync`, `collectAsync`, `foldAsync` and `TaskSeq.findAsync`, `pickAsync`, `chooseAsync`, `filterAsync`, which allows the applied function to be asynchronous.
+
+[See below](#current-set-of-taskseq-utility-functions) for a full list of currently implemented functions and their variants.
+
+### `taskSeq` computation expressions
+
+The `taskSeq` computation expression can be used just like using `seq`. On top of that, it adds support for working with tasks through `let!` and
+looping over a normal or asynchronous sequence (one that implements `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 the task sequence expression:
+
+### Installation
+
+Dotnet Nuget
+
+```cmd
+dotnet add package FSharp.Control.TaskSeq
+```
+
+For a specific project:
+
+```cmd
+dotnet add myproject.fsproj package FSharp.Control.TaskSeq
+```
+
+F# Interactive (FSI):
+
+```f#
+// latest version
+> #r "nuget: FSharp.Control.TaskSeq"
+
+// or with specific version
+> #r "nuget: FSharp.Control.TaskSeq, 0.2.2"
+```
+
+Paket:
-Not necessarily in order of importance:
+```cmd
+dotnet paket add FSharp.Control.TaskSeq --project
+```
+
+Package Manager:
+
+```cmd
+PM> NuGet\Install-Package FSharp.Control.TaskSeq
+```
+
+As package reference in `fsproj` or `csproj` file:
+
+```xml
+
+
+```
+
+### Examples
+
+```f#
+open System.IO
+
+open FSharp.Control
+
+// singleton is fine
+let hello = taskSeq { yield "Hello, World!" }
+
+// can be mixed with normal sequences
+let oneToTen = taskSeq { yield! [1..10] }
+
+// returns a delayed sequence of IAsyncEnumerable
+let allFilesAsLines() = taskSeq {
+ let files = Directory.EnumerateFiles(@"c:\temp")
+ for file in files do
+ // await
+ let! contents = File.ReadAllLinesAsync file
+ // return all lines
+ yield! contents
+}
+
+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))
+
+
+// infinite sequence
+let feedFromTwitter user pwd = taskSeq {
+ do! loginToTwitterAsync(user, pwd)
+ while true do
+ let! message = getNextNextTwitterMessageAsync()
+ yield message
+}
+```
+
+## Status & planning
+
+This project has stable features currently, but before we go full "version one", we'd like to complete the surface area. This section covers the status of that, with a full list of implmented functions below. Here's the short list:
- [x] Stabilize and battle-test `taskSeq` resumable code. **DONE**
- [x] A growing set of module functions `TaskSeq`, see below for progress. **DONE & IN PROGRESS**
- [x] Packaging and publishing on Nuget, **DONE, PUBLISHED SINCE: 7 November 2022**. See https://www.nuget.org/packages/FSharp.Control.TaskSeq
- [x] Add `Async` variants for functions taking HOF arguments. **DONE**
- [ ] Add generated docs to
-- [ ] Expand surface area based on `AsyncSeq`.
-- [ ] User requests?
+- [ ] Expand surface area based on `AsyncSeq`. **ONGOING**
+
+### Implementation progress
-## Implementation progress
+As of 9 November 2022: [Nuget package available][21]. In this phase, we will frequently update the package. Current:
-As of 6 November 2022:
+[](https://www.nuget.org/packages/FSharp.Control.TaskSeq/)
-### `taskSeq` CE
+### Progress `taskSeq` CE
-The _resumable state machine_ backing the `taskSeq` CE is now finished and _restartability_ (not to be confused with _resumability_) has been implemented and stabilized. Full support for empty task sequences is done. Focus is now on adding functionality there, like adding more useful overloads for `yield` and `let!`. Suggestions are welcome!
+The _resumable state machine_ backing the `taskSeq` CE is now finished and _restartability_ (not to be confused with _resumability_) has been implemented and stabilized. Full support for empty task sequences is done. Focus is now on adding functionality there, like adding more useful overloads for `yield` and `let!`. [Suggestions are welcome!][issues].
-### `TaskSeq` module functions
+### Progress and implemented `TaskSeq` module functions
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.
@@ -283,8 +394,7 @@ Command modifiers, like `release` and `debug`, can be specified with `-` or `/`
build help
```
-For more info, see this PR: https://github.com/abelbraaksma/TaskSeq/pull/29.
-
+For more info, see this PR: .
## Work in progress
@@ -294,285 +404,92 @@ On top of that, this library adds a set of `TaskSeq` module functions, with thei
## Current set of `TaskSeq` utility functions
-The following is the current surface area of the `TaskSeq` utility functions. This is just a dump of the signatures with doc comments
-to be used as a quick ref.
+The following are the current surface area of the `TaskSeq` utility functions, ordered alphabetically.
```f#
module TaskSeq =
- open System.Collections.Generic
- open System.Threading.Tasks
- open FSharp.Control.TaskSeqBuilders
-
- /// Initialize an empty taskSeq.
- val empty<'T> : taskSeq<'T>
-
- ///
- /// Returns if the task sequence contains no elements, otherwise.
- ///
- val isEmpty: taskSeq: taskSeq<'T> -> Task
-
- /// Returns taskSeq as an array. This function is blocking until the sequence is exhausted and will properly dispose of the resources.
- val toList: t: taskSeq<'T> -> 'T list
-
- /// Returns taskSeq as an array. This function is blocking until the sequence is exhausted and will properly dispose of the resources.
- val toArray: taskSeq: taskSeq<'T> -> 'T[]
-
- /// Returns taskSeq as a seq, similar to Seq.cached. This function is blocking until the sequence is exhausted and will properly dispose of the resources.
- val toSeqCached: taskSeq: taskSeq<'T> -> seq<'T>
-
- /// Unwraps the taskSeq as a Task>. This function is non-blocking.
- val toArrayAsync: taskSeq: taskSeq<'T> -> Task<'T[]>
-
- /// Unwraps the taskSeq as a Task>. This function is non-blocking.
- val toListAsync: taskSeq: taskSeq<'T> -> Task<'T list>
-
- /// Unwraps the taskSeq as a Task>. This function is non-blocking.
- val toResizeArrayAsync: taskSeq: taskSeq<'T> -> Task>
-
- /// Unwraps the taskSeq as a Task>. This function is non-blocking.
- val toIListAsync: taskSeq: taskSeq<'T> -> Task>
-
- /// Unwraps the taskSeq as a Task>. This function is non-blocking,
- /// exhausts the sequence and caches the results of the tasks in the sequence.
- val toSeqCachedAsync: taskSeq: taskSeq<'T> -> Task>
-
- /// Create a taskSeq of an array.
- val ofArray: array: 'T[] -> taskSeq<'T>
-
- /// Create a taskSeq of a list.
- val ofList: list: 'T list -> taskSeq<'T>
-
- /// Create a taskSeq of a seq.
- val ofSeq: sequence: seq<'T> -> taskSeq<'T>
-
- /// Create a taskSeq of a ResizeArray, aka List.
- val ofResizeArray: data: ResizeArray<'T> -> taskSeq<'T>
-
- /// Create a taskSeq of a sequence of tasks, that may already have hot-started.
- val ofTaskSeq: sequence: seq<#Task<'T>> -> taskSeq<'T>
-
- /// Create a taskSeq of a list of tasks, that may already have hot-started.
- val ofTaskList: list: #Task<'T> list -> taskSeq<'T>
-
- /// Create a taskSeq of an array of tasks, that may already have hot-started.
- val ofTaskArray: array: #Task<'T> array -> taskSeq<'T>
-
- /// Create a taskSeq of a seq of async.
- val ofAsyncSeq: sequence: seq> -> taskSeq<'T>
-
- /// Create a taskSeq of a list of async.
- val ofAsyncList: list: Async<'T> list -> taskSeq<'T>
-
- /// Create a taskSeq of an array of async.
- val ofAsyncArray: array: Async<'T> array -> taskSeq<'T>
-
- /// Iterates over the taskSeq applying the action function to each item. This function is non-blocking
- /// exhausts the sequence as soon as the task is evaluated.
- val iter: action: ('T -> unit) -> taskSeq: taskSeq<'T> -> Task
-
- /// Iterates over the taskSeq applying the action function to each item. This function is non-blocking,
- /// exhausts the sequence as soon as the task is evaluated.
- val iteri: action: (int -> 'T -> unit) -> taskSeq: taskSeq<'T> -> Task
-
- /// Iterates over the taskSeq applying the async action to each item. This function is non-blocking
- /// exhausts the sequence as soon as the task is evaluated.
- val iterAsync: action: ('T -> #Task) -> taskSeq: taskSeq<'T> -> Task
-
- /// Iterates over the taskSeq, applying the async action to each item. This function is non-blocking,
- /// exhausts the sequence as soon as the task is evaluated.
- val iteriAsync: action: (int -> 'T -> #Task) -> taskSeq: taskSeq<'T> -> Task
-
- /// Maps over the taskSeq, applying the mapper function to each item. This function is non-blocking.
- val map: mapper: ('T -> 'U) -> taskSeq: taskSeq<'T> -> taskSeq<'U>
-
- /// Maps over the taskSeq with an index, applying the mapper function to each item. This function is non-blocking.
- val mapi: mapper: (int -> 'T -> 'U) -> taskSeq: taskSeq<'T> -> taskSeq<'U>
-
- /// Maps over the taskSeq, applying the async mapper function to each item. This function is non-blocking.
- val mapAsync: mapper: ('T -> #Task<'U>) -> taskSeq: taskSeq<'T> -> taskSeq<'U>
-
- /// Maps over the taskSeq with an index, applying the async mapper function to each item. This function is non-blocking.
- val mapiAsync: mapper: (int -> 'T -> #Task<'U>) -> taskSeq: taskSeq<'T> -> taskSeq<'U>
-
- /// Applies the given function to the items in the taskSeq and concatenates all the results in order.
- val collect: binder: ('T -> #taskSeq<'U>) -> taskSeq: taskSeq<'T> -> taskSeq<'U>
-
- /// Applies the given function to the items in the taskSeq and concatenates all the results in order.
- val collectSeq: binder: ('T -> #seq<'U>) -> taskSeq: taskSeq<'T> -> taskSeq<'U>
-
- /// Applies the given async function to the items in the taskSeq and concatenates all the results in order.
- val collectAsync: binder: ('T -> #Task<'TSeqU>) -> taskSeq: taskSeq<'T> -> taskSeq<'U> when 'TSeqU :> taskSeq<'U>
-
- /// Applies the given async function to the items in the taskSeq and concatenates all the results in order.
- val collectSeqAsync: binder: ('T -> #Task<'SeqU>) -> taskSeq: taskSeq<'T> -> taskSeq<'U> when 'SeqU :> seq<'U>
-
- ///
- /// Returns the first element of the , or if the sequence is empty.
- ///
- /// Thrown when the sequence is empty.
- val tryHead: taskSeq: taskSeq<'T> -> Task<'T option>
-
- ///
- /// Returns the first element of the .
- ///
- /// Thrown when the sequence is empty.
- val head: taskSeq: taskSeq<'T> -> Task<'T>
-
- ///
- /// Returns the last element of the , or if the sequence is empty.
- ///
- /// Thrown when the sequence is empty.
- val tryLast: taskSeq: taskSeq<'T> -> Task<'T option>
-
- ///
- /// Returns the last element of the .
- ///
- /// Thrown when the sequence is empty.
- val last: taskSeq: taskSeq<'T> -> Task<'T>
-
- ///
- /// Returns the nth element of the , or if the sequence
- /// does not contain enough elements, or if is negative.
- /// Parameter is zero-based, that is, the value 0 returns the first element.
- ///
- val tryItem: index: int -> taskSeq: taskSeq<'T> -> Task<'T option>
-
- ///
- /// Returns the nth element of the , or if the sequence
- /// does not contain enough elements, or if is negative.
- ///
- /// Thrown when the sequence has insufficient length or
- /// is negative.
- val item: index: int -> taskSeq: taskSeq<'T> -> Task<'T>
-
- ///
- /// Returns the only element of the task sequence, or if the sequence is empty of
- /// contains more than one element.
- ///
- val tryExactlyOne: source: taskSeq<'T> -> Task<'T option>
-
- ///
- /// Returns the only element of the task sequence.
- ///
- /// Thrown when the input sequence does not contain precisely one element.
- val exactlyOne: source: taskSeq<'T> -> Task<'T>
-
- ///
- /// Applies the given function to each element of the task sequence. Returns
- /// a sequence comprised of the results "x" for each element where
- /// the function returns Some(x).
- /// If is asynchronous, consider using .
- ///
+ val append: source1: #taskSeq<'T> -> source2: #taskSeq<'T> -> taskSeq<'T>
+ val appendSeq: source1: #taskSeq<'T> -> source2: #seq<'T> -> taskSeq<'T>
+ val box: source: taskSeq<'T> -> taskSeq
+ val cast: source: taskSeq -> taskSeq<'T>
val choose: chooser: ('T -> 'U option) -> source: taskSeq<'T> -> taskSeq<'U>
-
- ///
- /// Applies the given asynchronous function to each element of the task sequence. Returns
- /// a sequence comprised of the results "x" for each element where
- /// the function returns .
- /// If does not need to be asynchronous, consider using .
- ///
val chooseAsync: chooser: ('T -> #Task<'U option>) -> source: taskSeq<'T> -> taskSeq<'U>
-
- ///
- /// Returns a new collection containing only the elements of the collection
- /// for which the given function returns .
- /// If is asynchronous, consider using .
- ///
+ val collect: binder: ('T -> #taskSeq<'U>) -> source: taskSeq<'T> -> taskSeq<'U>
+ val collectAsync: binder: ('T -> #Task<'TSeqU>) -> source: taskSeq<'T> -> taskSeq<'U> when 'TSeqU :> taskSeq<'U>
+ val collectSeq: binder: ('T -> #seq<'U>) -> source: taskSeq<'T> -> taskSeq<'U>
+ val collectSeqAsync: binder: ('T -> #Task<'SeqU>) -> source: taskSeq<'T> -> taskSeq<'U> when 'SeqU :> seq<'U>
+ 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 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>
+ val exceptOfSeq<'T when 'T: equality> : itemsToExclude: seq<'T> -> source: taskSeq<'T> -> taskSeq<'T>
+ val exists: predicate: ('T -> bool) -> source: taskSeq<'T> -> Task
+ val existsAsync: predicate: ('T -> #Task) -> source: taskSeq<'T> -> Task
val filter: predicate: ('T -> bool) -> source: taskSeq<'T> -> taskSeq<'T>
-
- ///
- /// Returns a new collection containing only the elements of the collection
- /// for which the given asynchronous function returns .
- /// If does not need to be asynchronous, consider using .
- ///
val filterAsync: predicate: ('T -> #Task) -> source: taskSeq<'T> -> taskSeq<'T>
-
- ///
- /// Applies the given function to successive elements of the task sequence
- /// in , returning the first result where the function returns .
- /// If is asynchronous, consider using .
- ///
- val tryPick: chooser: ('T -> 'U option) -> source: taskSeq<'T> -> Task<'U option>
-
- ///
- /// Applies the given asynchronous function to successive elements of the task sequence
- /// in , returning the first result where the function returns .
- /// If does not need to be asynchronous, consider using .
- ///
- val tryPickAsync: chooser: ('T -> #Task<'U option>) -> source: taskSeq<'T> -> Task<'U option>
-
- ///
- /// Returns the first element of the task sequence in for which the given function
- /// returns . Returns if no such element exists.
- /// If is asynchronous, consider using .
- ///
- val tryFind: predicate: ('T -> bool) -> source: taskSeq<'T> -> Task<'T option>
-
- ///
- /// Returns the first element of the task sequence in for which the given asynchronous function
- /// returns . Returns if no such element exists.
- /// If does not need to be asynchronous, consider using .
- ///
- val tryFindAsync: predicate: ('T -> #Task) -> source: taskSeq<'T> -> Task<'T option>
-
-
- ///
- /// Applies the given function to successive elements of the task sequence
- /// in , returning the first result where the function returns .
- /// If is asynchronous, consider using .
- /// Thrown when every item of the sequence
- /// evaluates to when the given function is applied.
- ///
- val pick: chooser: ('T -> 'U option) -> source: taskSeq<'T> -> Task<'U>
-
- ///
- /// Applies the given asynchronous function to successive elements of the task sequence
- /// in , returning the first result where the function returns .
- /// If does not need to be asynchronous, consider using .
- /// Thrown when every item of the sequence
- /// evaluates to when the given function is applied.
- ///
- val pickAsync: chooser: ('T -> #Task<'U option>) -> source: taskSeq<'T> -> Task<'U>
-
- ///
- /// Returns the first element of the task sequence in for which the given function
- /// returns .
- /// If is asynchronous, consider using .
- ///
- /// Thrown if no element returns when
- /// evaluated by the function.
val find: predicate: ('T -> bool) -> source: taskSeq<'T> -> Task<'T>
-
- ///
- /// Returns the first element of the task sequence in for which the given
- /// asynchronous function returns .
- /// If does not need to be asynchronous, consider using .
- ///
- /// Thrown if no element returns when
- /// evaluated by the function.
val findAsync: predicate: ('T -> #Task) -> source: taskSeq<'T> -> Task<'T>
-
- ///
- /// Zips two task sequences, returning a taskSeq of the tuples of each sequence, in order. May raise ArgumentException
- /// if the sequences are or unequal length.
- ///
- /// The sequences have different lengths.
- val zip: taskSeq1: taskSeq<'T> -> taskSeq2: taskSeq<'U> -> IAsyncEnumerable<'T * 'U>
-
- ///
- /// Applies the function to each element in the task sequence,
- /// threading an accumulator argument of type through the computation.
- /// If the accumulator function is asynchronous, consider using .
- ///
- val fold: folder: ('State -> 'T -> 'State) -> state: 'State -> taskSeq: taskSeq<'T> -> Task<'State>
-
- ///
- /// Applies the asynchronous function to each element in the task sequence,
- /// threading an accumulator argument of type through the computation.
- /// If the accumulator function does not need to be asynchronous, consider using .
- ///
- val foldAsync: folder: ('State -> 'T -> #Task<'State>) -> state: 'State -> taskSeq: taskSeq<'T> -> Task<'State>
-
+ val findIndex: predicate: ('T -> bool) -> source: taskSeq<'T> -> Task
+ val findIndexAsync: predicate: ('T -> #Task) -> source: taskSeq<'T> -> Task
+ val fold: folder: ('State -> 'T -> 'State) -> state: 'State -> source: taskSeq<'T> -> Task<'State>
+ val foldAsync: folder: ('State -> 'T -> #Task<'State>) -> state: 'State -> source: taskSeq<'T> -> Task<'State>
+ val head: source: taskSeq<'T> -> Task<'T>
+ val indexed: source: taskSeq<'T> -> taskSeq
+ val init: count: int -> initializer: (int -> 'T) -> taskSeq<'T>
+ val initAsync: count: int -> initializer: (int -> #Task<'T>) -> taskSeq<'T>
+ val initInfinite: initializer: (int -> 'T) -> taskSeq<'T>
+ val initInfiniteAsync: initializer: (int -> #Task<'T>) -> taskSeq<'T>
+ val isEmpty: source: taskSeq<'T> -> Task
+ val item: index: int -> source: taskSeq<'T> -> Task<'T>
+ val iter: action: ('T -> unit) -> source: taskSeq<'T> -> Task
+ val iterAsync: action: ('T -> #Task) -> source: taskSeq<'T> -> Task
+ val iteri: action: (int -> 'T -> unit) -> source: taskSeq<'T> -> Task
+ val iteriAsync: action: (int -> 'T -> #Task) -> source: taskSeq<'T> -> Task
+ val last: source: taskSeq<'T> -> Task<'T>
+ val length: source: taskSeq<'T> -> Task
+ val lengthBy: predicate: ('T -> bool) -> source: taskSeq<'T> -> Task
+ val lengthByAsync: predicate: ('T -> #Task) -> source: taskSeq<'T> -> Task
+ val lengthOrMax: max: int -> source: taskSeq<'T> -> Task
+ val map: mapper: ('T -> 'U) -> source: taskSeq<'T> -> taskSeq<'U>
+ val mapAsync: mapper: ('T -> #Task<'U>) -> source: taskSeq<'T> -> taskSeq<'U>
+ val mapi: mapper: (int -> 'T -> 'U) -> source: taskSeq<'T> -> taskSeq<'U>
+ val mapiAsync: mapper: (int -> 'T -> #Task<'U>) -> source: taskSeq<'T> -> taskSeq<'U>
+ val ofArray: source: 'T[] -> taskSeq<'T>
+ val ofAsyncArray: source: Async<'T> array -> taskSeq<'T>
+ val ofAsyncList: source: Async<'T> list -> taskSeq<'T>
+ val ofAsyncSeq: source: seq> -> taskSeq<'T>
+ val ofList: source: 'T list -> taskSeq<'T>
+ val ofResizeArray: source: ResizeArray<'T> -> taskSeq<'T>
+ val ofSeq: source: seq<'T> -> taskSeq<'T>
+ val ofTaskArray: source: #Task<'T> array -> taskSeq<'T>
+ val ofTaskList: source: #Task<'T> list -> taskSeq<'T>
+ val ofTaskSeq: source: seq<#Task<'T>> -> taskSeq<'T>
+ val pick: chooser: ('T -> 'U option) -> source: taskSeq<'T> -> Task<'U>
+ val pickAsync: chooser: ('T -> #Task<'U option>) -> source: taskSeq<'T> -> Task<'U>
+ val prependSeq: source1: #seq<'T> -> source2: #taskSeq<'T> -> taskSeq<'T>
+ val tail: source: taskSeq<'T> -> Task>
+ val toArray: source: taskSeq<'T> -> 'T[]
+ val toArrayAsync: source: taskSeq<'T> -> Task<'T[]>
+ val toIListAsync: source: taskSeq<'T> -> Task>
+ val toList: source: taskSeq<'T> -> 'T list
+ val toListAsync: source: taskSeq<'T> -> Task<'T list>
+ val toResizeArrayAsync: source: taskSeq<'T> -> Task>
+ val toSeq: source: taskSeq<'T> -> seq<'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>
+ val tryFindIndex: predicate: ('T -> bool) -> source: taskSeq<'T> -> Task
+ val tryFindIndexAsync: predicate: ('T -> #Task) -> source: taskSeq<'T> -> Task
+ val tryHead: source: taskSeq<'T> -> Task<'T option>
+ val tryItem: index: int -> source: taskSeq<'T> -> Task<'T option>
+ val tryLast: source: taskSeq<'T> -> Task<'T option>
+ val tryPick: chooser: ('T -> 'U option) -> source: taskSeq<'T> -> Task<'U option>
+ val tryPickAsync: chooser: ('T -> #Task<'U option>) -> source: taskSeq<'T> -> Task<'U option>
+ val tryTail: source: taskSeq<'T> -> Task option>
+ val unbox<'U when 'U: struct> : source: taskSeq -> taskSeq<'U>
+ val zip: source1: taskSeq<'T> -> source2: taskSeq<'U> -> taskSeq<'T * 'U>
```
[buildstatus]: https://github.com/abelbraaksma/TaskSeq/actions/workflows/main.yaml
@@ -600,6 +517,7 @@ 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
+[21]: https://www.nuget.org/packages/FSharp.Control.TaskSeq#versions-body-tab
[#2]: https://github.com/fsprojects/FSharp.Control.TaskSeq/pull/2
[#11]: https://github.com/fsprojects/FSharp.Control.TaskSeq/pull/11
@@ -614,3 +532,4 @@ module TaskSeq =
[#82]: https://github.com/fsprojects/FSharp.Control.TaskSeq/pull/82
[#83]: https://github.com/fsprojects/FSharp.Control.TaskSeq/pull/83
+[issues]: https://github.com/fsprojects/FSharp.Control.TaskSeq/issues
\ No newline at end of file
diff --git a/Version.props b/Version.props
new file mode 100644
index 00000000..7f8337f7
--- /dev/null
+++ b/Version.props
@@ -0,0 +1,5 @@
+
+
+ 0.2.2
+
+
\ No newline at end of file
diff --git a/src/FSharp.Control.TaskSeq.Test/TaskSeq.ico b/assets/TaskSeq.ico
similarity index 100%
rename from src/FSharp.Control.TaskSeq.Test/TaskSeq.ico
rename to assets/TaskSeq.ico
diff --git a/assets/nuget-package-readme.md b/assets/nuget-package-readme.md
new file mode 100644
index 00000000..6fa010bf
--- /dev/null
+++ b/assets/nuget-package-readme.md
@@ -0,0 +1,289 @@
+# TaskSeq
+
+An implementation of [`IAsyncEnumerable<'T>`][3] as a computation expression: `taskSeq { ... }` with an accompanying `TaskSeq` module.
+
+-----------------------------------------
+
+## Table of contents
+
+
+
+- [Overview](#overview)
+ - [Module functions](#module-functions)
+ - [`taskSeq` computation expressions](#taskseq-computation-expressions)
+ - [Examples](#examples)
+ - [`TaskSeq` module functions](#taskseq-module-functions)
+- [More information](#more-information)
+ - [Futher reading `IAsyncEnumerable`](#futher-reading-iasyncenumerable)
+ - [Futher reading on resumable state machines](#futher-reading-on-resumable-state-machines)
+ - [Further reading on computation expressions](#further-reading-on-computation-expressions)
+
+-----------------------------------------
+
+## Overview
+
+The `IAsyncEnumerable` interface was added to .NET in `.NET Core 3.0` and is part of `.NET Standard 2.1`. The main use-case was for iterative asynchronous enumeration over some resource. For instance, an event stream or a REST API interface with pagination, asynchronous reading over a list of files and accumulating the results, where each action can be modeled as a [`MoveNextAsync`][4] call on the [`IAsyncEnumerator<'T>`][5] given by a call to [`GetAsyncEnumerator()`][6].
+
+Since the introduction of `task` in F# the call for a native implementation of _task sequences_ has grown, in particular because proper iterating over an `IAsyncEnumerable` has proven challenging, especially if one wants to avoid mutable variables. This library is an answer to that call and implements the same _resumable state machine_ approach with `taskSeq`.
+
+### Module functions
+
+As with `seq` and `Seq`, this library comes with a bunch of well-known collection functions, like `TaskSeq.empty`, `isEmpty` or `TaskSeq.map`, `iter`, `collect`, `fold` and `TaskSeq.find`, `pick`, `choose`, `filter`. Where applicable, these come with async variants, like `TaskSeq.mapAsync` `iterAsync`, `collectAsync`, `foldAsync` and `TaskSeq.findAsync`, `pickAsync`, `chooseAsync`, `filterAsync`, which allows the applied function to be asynchronous.
+
+[See below](#taskseq-module-functions) for a full list of currently implemented functions and their variants.
+
+### `taskSeq` computation expressions
+
+The `taskSeq` computation expression can be used just like using `seq`. On top of that, it adds support for working with tasks through `let!` and
+looping over a normal or asynchronous sequence (one that implements `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 the task sequence expression.
+
+### Examples
+
+```f#
+open System.IO
+
+open FSharp.Control
+
+// singleton is fine
+let hello = taskSeq { yield "Hello, World!" }
+
+// can be mixed with normal sequences
+let oneToTen = taskSeq { yield! [1..10] }
+
+// returns a delayed sequence of IAsyncEnumerable
+let allFilesAsLines() = taskSeq {
+ let files = Directory.EnumerateFiles(@"c:\temp")
+ for file in files do
+ // await
+ let! contents = File.ReadAllLinesAsync file
+ // return all lines
+ yield! contents
+}
+
+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))
+
+
+// infinite sequence
+let feedFromTwitter user pwd = taskSeq {
+ do! loginToTwitterAsync(user, pwd)
+ while true do
+ let! message = getNextNextTwitterMessageAsync()
+ yield message
+}
+```
+
+### `TaskSeq` module functions
+
+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.") |
+| | `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` | | |
+
+#### 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._
+
+#### 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._
+
+#### 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._
+
+## More information
+
+### Futher reading `IAsyncEnumerable`
+
+- A good C#-based introduction [can be found in this blog][8].
+- [An MSDN article][9] written shortly after it was introduced.
+- Converting a `seq` to an `IAsyncEnumerable` [demo gist][10] as an example, though `TaskSeq` contains many more utility functions and uses a slightly different approach.
+- If you're looking for using `IAsyncEnumerable` with `async` and not `task`, the excellent [`AsyncSeq`][11] library should be used. While `TaskSeq` is intended to consume `async` just like `task` does, it won't create an `AsyncSeq` type (at least not yet). If you want classic Async and parallelism, you should get this library instead.
+
+### Futher reading on resumable state machines
+
+- A state machine from a monadic perspective in F# [can be found here][12], which works with the pre-F# 6.0 non-resumable internals.
+- The [original RFC for F# 6.0 on resumable state machines][13]
+- The [original RFC for introducing `task`][14] to F# 6.0.
+- A [pre F# 6.0 `TaskBuilder`][15] that motivated the `task` CE later added to F# Core.
+- [MSDN Documentation on `task`][16] and [`async`][17].
+
+### Further reading on computation expressions
+
+- [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.
+
+[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
+[6]: https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.iasyncenumerable-1.getasyncenumerator?view=net-7.0
+[7]: https://learn.microsoft.com/en-us/dotnet/api/system.iasyncdisposable?view=net-7.0
+[8]: https://stu.dev/iasyncenumerable-introduction/
+[9]: https://learn.microsoft.com/en-us/archive/msdn-magazine/2019/november/csharp-iterating-with-async-enumerables-in-csharp-8
+[10]: https://gist.github.com/akhansari/d88812b742aa6be1c35b4f46bd9f8532
+[11]: https://fsprojects.github.io/FSharp.Control.AsyncSeq/AsyncSeq.html
+[12]: http://blumu.github.io/ResumableMonad/TheResumableMonad.html
+[13]: https://github.com/fsharp/fslang-design/blob/main/FSharp-6.0/FS-1087-resumable-code.md
+[14]: https://github.com/fsharp/fslang-design/blob/main/FSharp-6.0/FS-1097-task-builder.md
+[15]: https://github.com/rspeele/TaskBuilder.fs/
+[16]: https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/task-expressions
+[17]: https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/async-expressions
+[18]: https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/computation-expressions
+[19]: https://fsharpforfunandprofit.com/series/computation-expressions/
+
+[#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
+[#82]: https://github.com/fsprojects/FSharp.Control.TaskSeq/pull/82
+[#83]: https://github.com/fsprojects/FSharp.Control.TaskSeq/pull/83
\ No newline at end of file
diff --git a/assets/taskseq-icon.png b/assets/taskseq-icon.png
new file mode 100644
index 00000000..b14a77c1
Binary files /dev/null and b/assets/taskseq-icon.png differ
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 9914b72c..a0760a87 100644
--- a/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj
+++ b/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj
@@ -1,14 +1,15 @@
-
+
net6.0
false
false
- TaskSeq.ico
+ ..\..\assets\TaskSeq.ico
+
@@ -50,11 +51,10 @@
-
-
-
-
-
+
+
+
+
diff --git a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Append.Tests.fs b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Append.Tests.fs
index 0448acc7..629dd777 100644
--- a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Append.Tests.fs
+++ b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Append.Tests.fs
@@ -17,8 +17,8 @@ open System.Collections.Generic
let validateSequence ts =
ts
- |> TaskSeq.toSeqCachedAsync
- |> Task.map (Seq.map string)
+ |> TaskSeq.toListAsync
+ |> Task.map (List.map string)
|> Task.map (String.concat "")
|> Task.map (should equal "1234567891012345678910")
diff --git a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Cast.Tests.fs b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Cast.Tests.fs
index 2dfaa57b..378253f4 100644
--- a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Cast.Tests.fs
+++ b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Cast.Tests.fs
@@ -17,8 +17,8 @@ open FSharp.Control
/// Asserts that a sequence contains the char values 'A'..'J'.
let validateSequence ts =
ts
- |> TaskSeq.toSeqCachedAsync
- |> Task.map (Seq.map string)
+ |> TaskSeq.toListAsync
+ |> Task.map (List.map string)
|> Task.map (String.concat "")
|> Task.map (should equal "12345678910")
diff --git a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Collect.Tests.fs b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Collect.Tests.fs
index fd3d7676..ba975595 100644
--- a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Collect.Tests.fs
+++ b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Collect.Tests.fs
@@ -102,8 +102,8 @@ module Immutable =
let validateSequence ts =
ts
- |> TaskSeq.toSeqCachedAsync
- |> Task.map (Seq.map string)
+ |> TaskSeq.toListAsync
+ |> Task.map (List.map string)
|> Task.map (String.concat "")
|> Task.map (should equal "ABBCCDDEEFFGGHHIIJJK")
diff --git a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Concat.Tests.fs b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Concat.Tests.fs
index df4c07e8..cfbd9464 100644
--- a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Concat.Tests.fs
+++ b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Concat.Tests.fs
@@ -15,8 +15,8 @@ open System.Collections.Generic
let validateSequence ts =
ts
- |> TaskSeq.toSeqCachedAsync
- |> Task.map (Seq.map string)
+ |> TaskSeq.toListAsync
+ |> Task.map (List.map string)
|> Task.map (String.concat "")
|> Task.map (should equal "123456789101234567891012345678910")
diff --git a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Delay.Tests.fs b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Delay.Tests.fs
index 20026528..a13c9111 100644
--- a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Delay.Tests.fs
+++ b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Delay.Tests.fs
@@ -15,8 +15,8 @@ open System.Collections.Generic
let validateSequence ts =
ts
- |> TaskSeq.toSeqCachedAsync
- |> Task.map (Seq.map string)
+ |> TaskSeq.toListAsync
+ |> Task.map (List.map string)
|> Task.map (String.concat "")
|> Task.map (should equal "12345678910")
diff --git a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Empty.Tests.fs b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Empty.Tests.fs
index a9477eb1..ccdba831 100644
--- a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Empty.Tests.fs
+++ b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Empty.Tests.fs
@@ -10,7 +10,7 @@ open FSharp.Control
[]
let ``TaskSeq-empty returns an empty sequence`` () = task {
- let! sq = TaskSeq.empty |> TaskSeq.toSeqCachedAsync
+ let! sq = TaskSeq.empty |> TaskSeq.toListAsync
Seq.isEmpty sq |> should be True
Seq.length sq |> should equal 0
}
diff --git a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Map.Tests.fs b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Map.Tests.fs
index c9a1ab1b..4793684e 100644
--- a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Map.Tests.fs
+++ b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Map.Tests.fs
@@ -16,8 +16,8 @@ open FSharp.Control
/// Asserts that a sequence contains the char values 'A'..'J'.
let validateSequence ts =
ts
- |> TaskSeq.toSeqCachedAsync
- |> Task.map (Seq.map string)
+ |> TaskSeq.toListAsync
+ |> Task.map (List.map string)
|> Task.map (String.concat "")
|> Task.map (should equal "ABCDEFGHIJ")
@@ -29,8 +29,8 @@ let validateSequenceWithOffset offset ts =
|> String.concat ""
ts
- |> TaskSeq.toSeqCachedAsync
- |> Task.map (Seq.map string)
+ |> TaskSeq.toListAsync
+ |> Task.map (List.map string)
|> Task.map (String.concat "")
|> Task.map (should equal expected)
diff --git a/src/FSharp.Control.TaskSeq.Test/TaskSeq.ToXXX.Tests.fs b/src/FSharp.Control.TaskSeq.Test/TaskSeq.ToXXX.Tests.fs
index 42656317..fbb5635b 100644
--- a/src/FSharp.Control.TaskSeq.Test/TaskSeq.ToXXX.Tests.fs
+++ b/src/FSharp.Control.TaskSeq.Test/TaskSeq.ToXXX.Tests.fs
@@ -35,13 +35,6 @@ module EmptySeq =
results |> should be Empty
}
- [)>]
- let ``TaskSeq-toSeqCachedAsync with empty`` variant = task {
- let tq = Gen.getEmptyVariant variant
- let! (results: seq<_>) = tq |> TaskSeq.toSeqCachedAsync
- results |> Seq.toArray |> should be Empty
- }
-
[)>]
let ``TaskSeq-toIListAsync with empty`` variant = task {
let tq = Gen.getEmptyVariant variant
@@ -71,7 +64,7 @@ module EmptySeq =
[)>]
let ``TaskSeq-toSeqCached with empty`` variant =
let tq = Gen.getEmptyVariant variant
- let (results: seq<_>) = tq |> TaskSeq.toSeqCached
+ let (results: seq<_>) = tq |> TaskSeq.toSeq
results |> Seq.toArray |> should be Empty
module Immutable =
@@ -90,13 +83,6 @@ module Immutable =
results |> should equal [ 1..10 ]
}
- [)>]
- let ``TaskSeq-toSeqCachedAsync should succeed`` variant = task {
- let tq = Gen.getSeqImmutable variant
- let! (results: seq<_>) = tq |> TaskSeq.toSeqCachedAsync
- results |> Seq.toArray |> should equal [| 1..10 |]
- }
-
[)>]
let ``TaskSeq-toIListAsync should succeed`` variant = task {
let tq = Gen.getSeqImmutable variant
@@ -126,7 +112,7 @@ module Immutable =
[)>]
let ``TaskSeq-toSeqCached should succeed and be blocking`` variant =
let tq = Gen.getSeqImmutable variant
- let (results: seq<_>) = tq |> TaskSeq.toSeqCached
+ let (results: seq<_>) = tq |> TaskSeq.toSeq
results |> Seq.toArray |> should equal [| 1..10 |]
@@ -159,15 +145,6 @@ module SideEffects =
results2 |> should equal [ 11..20 ]
}
- [)>]
- let ``TaskSeq-toSeqCachedAsync should execute side effects multiple times`` variant = task {
- let tq = Gen.getSeqWithSideEffect variant
- let! (results1: seq<_>) = tq |> TaskSeq.toSeqCachedAsync
- let! (results2: seq<_>) = tq |> TaskSeq.toSeqCachedAsync
- results1 |> Seq.toArray |> should equal [| 1..10 |]
- results2 |> Seq.toArray |> should equal [| 11..20 |]
- }
-
[)>]
let ``TaskSeq-toIListAsync should execute side effects multiple times`` variant = task {
let tq = Gen.getSeqWithSideEffect variant
@@ -205,7 +182,7 @@ module SideEffects =
[)>]
let ``TaskSeq-toSeqCached should execute side effects multiple times`` variant =
let tq = Gen.getSeqWithSideEffect variant
- let (results1: seq<_>) = tq |> TaskSeq.toSeqCached
- let (results2: seq<_>) = tq |> TaskSeq.toSeqCached
+ let (results1: seq<_>) = tq |> TaskSeq.toSeq
+ let (results2: seq<_>) = tq |> TaskSeq.toSeq
results1 |> Seq.toArray |> should equal [| 1..10 |]
results2 |> Seq.toArray |> should equal [| 11..20 |]
diff --git a/src/FSharp.Control.TaskSeq.Test/TestUtils.fs b/src/FSharp.Control.TaskSeq.Test/TestUtils.fs
index ef50dba8..91baf383 100644
--- a/src/FSharp.Control.TaskSeq.Test/TestUtils.fs
+++ b/src/FSharp.Control.TaskSeq.Test/TestUtils.fs
@@ -138,8 +138,8 @@ type DummyTaskFactory(µsecMin: int64<µs>, µsecMax: int64<µs>) =
module TestUtils =
let verifyEmpty ts =
ts
- |> TaskSeq.toSeqCachedAsync
- |> Task.map (Seq.isEmpty >> should be True)
+ |> TaskSeq.toArrayAsync
+ |> Task.map (Array.isEmpty >> should be True)
/// Delays (no spin-wait!) between 20 and 70ms, assuming a 15.6ms resolution clock
let longDelay () = task { do! Task.Delay(Random().Next(20, 70)) }
diff --git a/src/FSharp.Control.TaskSeq.sln b/src/FSharp.Control.TaskSeq.sln
index 2df77210..2e1e32f6 100644
--- a/src/FSharp.Control.TaskSeq.sln
+++ b/src/FSharp.Control.TaskSeq.sln
@@ -8,8 +8,11 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B252135E-C676-4542-8B72-412DF1B9487C}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
+ ..\.gitignore = ..\.gitignore
..\build.cmd = ..\build.cmd
+ ..\Directory.Build.props = ..\Directory.Build.props
..\README.md = ..\README.md
+ ..\Version.props = ..\Version.props
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{E55512EE-8DE2-4B44-9A4A-CF779734160B}"
@@ -17,11 +20,19 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{
..\.github\workflows\build.yaml = ..\.github\workflows\build.yaml
..\.github\dependabot.yml = ..\.github\dependabot.yml
..\.github\workflows\main.yaml = ..\.github\workflows\main.yaml
+ ..\.github\workflows\publish.yaml = ..\.github\workflows\publish.yaml
..\.github\workflows\test.yaml = ..\.github\workflows\test.yaml
EndProjectSection
EndProject
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.Control.TaskSeq.Test", "FSharp.Control.TaskSeq.Test\FSharp.Control.TaskSeq.Test.fsproj", "{06CA2C7E-04DA-4A85-BB8E-4D94BD67AEB3}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "assets", "assets", "{B198D5FE-A731-4AFA-96A5-E5DD94EE293D}"
+ ProjectSection(SolutionItems) = preProject
+ ..\assets\nuget-package-readme.md = ..\assets\nuget-package-readme.md
+ ..\assets\taskseq-icon.png = ..\assets\taskseq-icon.png
+ ..\assets\TaskSeq.ico = ..\assets\TaskSeq.ico
+ EndProjectSection
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -42,6 +53,7 @@ Global
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{E55512EE-8DE2-4B44-9A4A-CF779734160B} = {B252135E-C676-4542-8B72-412DF1B9487C}
+ {B198D5FE-A731-4AFA-96A5-E5DD94EE293D} = {B252135E-C676-4542-8B72-412DF1B9487C}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {2AE57787-A847-4460-A627-1EB1D224FBC3}
diff --git a/src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj b/src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj
index ddba9897..d0298601 100644
--- a/src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj
+++ b/src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj
@@ -1,20 +1,67 @@
- net6.0
+ netstandard2.1
true
- TaskSeq.ico
-
+ ..\..\assets\TaskSeq.ico
+ True
+ Computation expression 'taskSeq' for processing IAsyncEnumerable sequences and module functions
+ $(Version)
+ Abel Braaksma; Don Syme
+ This library brings C#'s concept of 'await foreach' to F#, with a seamless implementation of IAsyncEnumerable<'T>.
-
-
-
+The 'taskSeq' computation expression adds support for awaitable asyncronous sequences with a similar ease of use and performance as F#'s 'task' CE, with minimal overhead through ValueTask under the hood. TaskSeq brings 'seq' and 'task' together in a safe way.
-
-
+Generates optimized IL code through the new resumable state machines, and comes with a comprehensive set of helpful functions in module 'TaskSeq'. See README for documentation and more info.
+ Copyright 2022
+ https://github.com/fsprojects/FSharp.Control.TaskSeq
+ https://github.com/fsprojects/FSharp.Control.TaskSeq
+ taskseq-icon.png
+ ..\..\packages
+ MIT
+ False
+ nuget-package-readme.md
+
+ Release notes:
+ 0.2.2
+ - removes TaskSeq.toSeqCachedAsync, which was incorrectly named. Use toSeq or toListAsync instead.
+ - renames TaskSeq.toSeqCached to TaskSeq.toSeq, which was its actual operational behavior.
+ 0.2.1
+ - fixes an issue with ValueTask on completed iterations.
+ - adds `TaskSeq.except` and `TaskSeq.exceptOfSeq` async set operations.
+ 0.2
+ - moved from NET 6.0, to NetStandard 2.1 for greater compatibility, no functional changes.
+ - move to minimally necessary FSharp.Core version: 6.0.2.
+ - updated readme with progress overview, corrected meta info, added release notes.
+ 0.1.1
+ - updated meta info in nuget package and added readme.
+ 0.1
+ - initial release
+ - implements taskSeq CE using resumable state machines
+ - with support for: yield, yield!, let, let!, while, for, try-with, try-finally, use, use!
+ - and: tasks and valuetasks
+ - adds toXXX / ofXXX functions
+ - adds map/mapi/fold/iter/iteri/collect etc with async variants
+ - adds find/pick/choose/filter etc with async variants and 'try' variants
+ - adds cast/concat/append/prepend/delay/exactlyOne
+ - adds empty/isEmpty
+ - adds findIndex/indexed/init/initInfinite
+ - adds head/last/tryHead/tryLast/tail/tryTail
+ - adds zip/length
+
+
+ taskseq'fsharp;f#;computation expression;IAsyncEnumerable;task;async;asyncseq;
+ True
+ snupkg
+
+
+ True
+ \
+
+
@@ -22,8 +69,13 @@
+
+
+
+
+
-
+
+
-
diff --git a/src/FSharp.Control.TaskSeq/TaskSeq.fs b/src/FSharp.Control.TaskSeq/TaskSeq.fs
index 9fb7e314..b94fbb26 100644
--- a/src/FSharp.Control.TaskSeq/TaskSeq.fs
+++ b/src/FSharp.Control.TaskSeq/TaskSeq.fs
@@ -46,7 +46,7 @@ module TaskSeq =
e.DisposeAsync().AsTask().Wait()
|]
- let toSeqCached (source: taskSeq<'T>) = seq {
+ let toSeq (source: taskSeq<'T>) = seq {
let e = source.GetAsyncEnumerator(CancellationToken())
try
@@ -66,8 +66,6 @@ module TaskSeq =
let toIListAsync source = Internal.toResizeArrayAndMapAsync (fun x -> x :> IList<_>) source
- let toSeqCachedAsync source = Internal.toResizeArrayAndMapAsync (fun x -> x :> seq<_>) source
-
//
// Convert 'OfXXX' functions
//
diff --git a/src/FSharp.Control.TaskSeq/TaskSeq.fsi b/src/FSharp.Control.TaskSeq/TaskSeq.fsi
index dcbab450..aeae538d 100644
--- a/src/FSharp.Control.TaskSeq/TaskSeq.fsi
+++ b/src/FSharp.Control.TaskSeq/TaskSeq.fsi
@@ -154,8 +154,16 @@ module TaskSeq =
/// Returns taskSeq as an array. This function is blocking until the sequence is exhausted and will properly dispose of the resources.
val toArray: source: taskSeq<'T> -> 'T[]
- /// Returns taskSeq as a seq, similar to Seq.cached. This function is blocking until the sequence is exhausted and will properly dispose of the resources.
- val toSeqCached: source: taskSeq<'T> -> seq<'T>
+ ///
+ /// Returns the task sequence as an F# , that is, an
+ /// . This function is blocking at each , but otherwise
+ /// acts as a normal delay-executed sequence.
+ /// It will then dispose of the resources.
+ ///
+ ///
+ /// The input task sequence.
+ /// The resulting task sequence.
+ val toSeq: source: taskSeq<'T> -> seq<'T>
/// Unwraps the taskSeq as a Task>. This function is non-blocking.
val toArrayAsync: source: taskSeq<'T> -> Task<'T[]>
@@ -169,10 +177,6 @@ module TaskSeq =
/// Unwraps the taskSeq as a Task>. This function is non-blocking.
val toIListAsync: source: taskSeq<'T> -> Task>
- /// Unwraps the taskSeq as a Task>. This function is non-blocking,
- /// exhausts the sequence and caches the results of the tasks in the sequence.
- val toSeqCachedAsync: source: taskSeq<'T> -> Task>
-
/// Create a taskSeq of an array.
val ofArray: source: 'T[] -> taskSeq<'T>
diff --git a/src/FSharp.Control.TaskSeq/TaskSeq.ico b/src/FSharp.Control.TaskSeq/TaskSeq.ico
deleted file mode 100644
index 65d3ee0a..00000000
Binary files a/src/FSharp.Control.TaskSeq/TaskSeq.ico and /dev/null differ
diff --git a/src/FSharp.Control.TaskSeq/Utils.fs b/src/FSharp.Control.TaskSeq/Utils.fs
index ecc2f685..74bd115a 100644
--- a/src/FSharp.Control.TaskSeq/Utils.fs
+++ b/src/FSharp.Control.TaskSeq/Utils.fs
@@ -2,6 +2,15 @@ namespace FSharp.Control
open System.Threading.Tasks
+[]
+module ValueTaskExtensions =
+ /// Extensions for ValueTask that are not available in NetStandard 2.1, but are
+ /// available in .NET 5+.
+ type ValueTask with
+
+ /// (Extension member) Gets a task that has already completed successfully.
+ static member inline CompletedTask = Unchecked.defaultof
+
module Task =
/// Convert an Async<'T> into a Task<'T>
let inline ofAsync (async: Async<'T>) = task { return! async }