FSharp.Control.TaskSeq
0.4.0
dotnet add package FSharp.Control.TaskSeq --version 0.4.0
NuGet\Install-Package FSharp.Control.TaskSeq -Version 0.4.0
<PackageReference Include="FSharp.Control.TaskSeq" Version="0.4.0" />
<PackageVersion Include="FSharp.Control.TaskSeq" Version="0.4.0" />
<PackageReference Include="FSharp.Control.TaskSeq" />
paket add FSharp.Control.TaskSeq --version 0.4.0
#r "nuget: FSharp.Control.TaskSeq, 0.4.0"
#:package FSharp.Control.TaskSeq@0.4.0
#addin nuget:?package=FSharp.Control.TaskSeq&version=0.4.0
#tool nuget:?package=FSharp.Control.TaskSeq&version=0.4.0
TaskSeq
An implementation of IAsyncEnumerable<'T> as a computation expression: taskSeq { ... } with an accompanying TaskSeq module and functions, that allow seamless use of asynchronous sequences similar to F#'s native seq and task CE's.
This readme covers the highlights and a summary of implemented functions. A more extensive overview can be found in the repository's readme.
Table of contents
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, sequential 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 call on the IAsyncEnumerator<'T> given by a call to GetAsyncEnumerator().
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 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
open System.IO
open FSharp.Control
// singleton is fine
let helloTs = taskSeq { yield "Hello, World!" }
// cold-started, that is, delay-executed
let f() = task {
// using toList forces execution of whole sequence
let! hello = TaskSeq.toList helloTs // toList returns a Task<'T list>
return List.head hello
}
// can be mixed with normal sequences
let oneToTen = taskSeq { yield! [1..10] }
// can be used with F#'s task and async in a for-loop
let f() = task { for x in oneToTen do printfn "Number %i" x }
let g() = async { for x in oneToTen do printfn "Number %i" x }
// returns a delayed sequence of IAsyncEnumerable<string>
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 by AsyncSeq. Each implemented function is documented through XML doc comments to provide the necessary context-sensitive help.
This is what has been implemented so far, is planned or skipped:
| Done | Seq |
TaskSeq |
Variants | Remarks |
|---|---|---|---|---|
| ❓ | allPairs |
allPairs |
note #1 | |
| ✅ #81 | append |
append |
||
| ✅ #81 | appendSeq |
|||
| ✅ #81 | prependSeq |
|||
average |
average |
|||
averageBy |
averageBy |
averageByAsync |
||
| ❓ | cache |
cache |
note #1 | |
| ✅ #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 |
||
| ✅ #237 | concat (list) |
concat (list) |
||
| ✅ #237 | concat (array) |
concat (array) |
||
| ✅ #237 | concat (r-array) |
concat (r-array) |
||
| ✅ #237 | concat (seq) |
concat (seq) |
||
| ✅ #70 | contains |
contains |
||
| ✅ #82 | delay |
delay |
||
distinct |
distinct |
|||
distinctBy |
dictinctBy |
distinctByAsync |
||
| ✅ #209 | drop |
|||
| ✅ #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 | ||
| ✅ #68 | findIndex |
findIndex |
findIndexAsync |
|
| 🚫 | findIndexBack |
n/a | n/a | note #2 |
| ✅ #2 | fold |
fold |
foldAsync |
|
fold2 |
fold2 |
fold2Async |
||
| 🚫 | foldBack |
note #2 | ||
| 🚫 | foldBack2 |
note #2 | ||
| ✅ #240 | forall |
forall |
forallAsync |
|
forall2 |
forall2 |
forall2Async |
||
| ❓ | groupBy |
groupBy |
groupByAsync |
note #1 |
| ✅ #23 | head |
head |
||
| ✅ #68 | indexed |
indexed |
||
| ✅ #69 | init |
init |
initAsync |
|
| ✅ #69 | initInfinite |
initInfinite |
initInfiniteAsync |
|
| ✅ #236 | insertAt |
insertAt |
||
| ✅ #236 | 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 | ||
| ✅ #2 | mapi |
mapi |
mapiAsync |
|
mapi2 |
mapi2 |
mapi2Async |
||
| ✅ #221 | max |
max |
||
| ✅ #221 | maxBy |
maxBy |
maxByAsync |
|
| ✅ #221 | min |
min |
||
| ✅ #221 | 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 | ||
reduce |
reduce |
reduceAsync |
||
| 🚫 | reduceBack |
note #2 | ||
| ✅ #236 | removeAt |
removeAt |
||
| ✅ #236 | removeManyAt |
removeManyAt |
||
replicate |
replicate |
|||
| ❓ | rev |
note #1 | ||
scan |
scan |
scanAsync |
||
| 🚫 | scanBack |
note #2 | ||
| ✅ #90 | singleton |
singleton |
||
| ✅ #209 | skip |
skip |
||
| ✅ #219 | skipWhile |
skipWhile |
skipWhileAsync |
|
| ✅ #219 | skipWhileInclusive |
skipWhileInclusiveAsync |
||
| ❓ | sort |
note #1 | ||
| ❓ | sortBy |
note #1 | ||
| ❓ | sortByAscending |
note #1 | ||
| ❓ | sortByDescending |
note #1 | ||
| ❓ | sortWith |
note #1 | ||
splitInto |
splitInto |
|||
sum |
sum |
|||
sumBy |
sumBy |
sumByAsync |
||
| ✅ #76 | tail |
tail |
||
| ✅ #209 | take |
take |
||
| ✅ #126 | takeWhile |
takeWhile |
takeWhileAsync |
|
| ✅ #126 | takeWhileInclusive |
takeWhileInclusiveAsync |
||
| ✅ #2 | toArray |
toArray |
toArrayAsync |
|
| ✅ #2 | toIList |
toIListAsync |
||
| ✅ #2 | toList |
toList |
toListAsync |
|
| ✅ #2 | toResizeArray |
toResizeArrayAsync |
||
| ✅ #2 | toSeq |
toSeqAsync |
||
| […] | ||||
| ❓ | transpose |
note #1 | ||
| ✅ #209 | truncate |
truncate |
||
| ✅ #23 | tryExactlyOne |
tryExactlyOne |
tryExactlyOneAsync |
|
| ✅ #23 | tryFind |
tryFind |
tryFindAsync |
|
| 🚫 | tryFindBack |
note #2 | ||
| ✅ #68 | tryFindIndex |
tryFindIndex |
tryFindIndexAsync |
|
| 🚫 | tryFindIndexBack |
note #2 | ||
| ✅ #23 | tryHead |
tryHead |
||
| ✅ #23 | tryItem |
tryItem |
||
| ✅ #23 | tryLast |
tryLast |
||
| ✅ #23 | tryPick |
tryPick |
tryPickAsync |
|
| ✅ #76 | tryTail |
|||
unfold |
unfold |
unfoldAsync |
||
| ✅ #236 | updateAt |
updateAt |
||
| ✅ #217 | 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
Further reading IAsyncEnumerable
- A good C#-based introduction can be found in this blog.
- An MSDN article written shortly after it was introduced.
- Converting a
seqto anIAsyncEnumerabledemo gist as an example, thoughTaskSeqcontains many more utility functions and uses a slightly different approach. - If you're looking for using
IAsyncEnumerablewithasyncand nottask, the excellentAsyncSeqlibrary should be used. WhileTaskSeqis intended to consumeasyncjust liketaskdoes, it won't create anAsyncSeqtype (at least not yet). If you want classic Async and parallelism, you should get this library instead.
Further reading on resumable state machines
- A state machine from a monadic perspective in F# can be found here, which works with the pre-F# 6.0 non-resumable internals.
- The original RFC for F# 6.0 on resumable state machines
- The original RFC for introducing
taskto F# 6.0. - A pre F# 6.0
TaskBuilderthat motivated thetaskCE later added to F# Core. - MSDN Documentation on
taskandasync.
Further reading on computation expressions
- Docs on MSDN form a good summary and starting point.
- Arguably the best step-by-step tutorial to using and building computation expressions by Scott Wlaschin.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.1 is compatible. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.1
- FSharp.Core (>= 6.0.1)
NuGet packages (14)
Showing the top 5 NuGet packages that depend on FSharp.Control.TaskSeq:
| Package | Downloads |
|---|---|
|
Propulsion
Efficient event streaming pipelines |
|
|
Equinox.CosmosStore
Efficient event sourced decisions and data |
|
|
Equinox.EventStore
Efficient event sourced decisions and data |
|
|
Propulsion.Feed
Efficient event streaming pipelines |
|
|
Equinox.DynamoStore
Efficient event sourced decisions and data |
GitHub repositories (1)
Showing the top 1 popular GitHub repositories that depend on FSharp.Control.TaskSeq:
| Repository | Stars |
|---|---|
|
nozzlegear/ShopifySharp
ShopifySharp is a .NET library that helps developers easily authenticate with and manage Shopify stores using Shopify's GraphQL API.
|
Release notes:
0.4.0
- overhaul all doc comments, add exceptions, improve IDE quick-info experience, #136, #220, #234
- new surface area functions, fixes #208:
* TaskSeq.take, skip, #209
* TaskSeq.truncate, drop, #209
* TaskSeq.where, whereAsync, #217
* TaskSeq.skipWhile, skipWhileInclusive, skipWhileAsync, skipWhileInclusiveAsync, #219
* TaskSeq.max, min, maxBy, minBy, maxByAsync, minByAsync, #221
* TaskSeq.insertAt, insertManyAt, removeAt, removeManyAt, updateAt, #236
* TaskSeq.forall, forallAsync, #240
* TaskSeq.concat (overloads: seq, array, resizearray, list), #237
- Performance: less thread hops with 'StartImmediateAsTask' instead of 'StartAsTask', fixes #135
- Performance: several inline and allocation improvements
- BINARY INCOMPATIBILITY: 'TaskSeq' module replaced by static members on 'TaskSeq<_>', fixes #184
- DEPRECATIONS (warning FS0044):
- type 'taskSeq<_>' is renamed to 'TaskSeq<_>', fixes #193
- function 'ValueTask.ofIValueTaskSource` renamed to `ValueTask.ofSource`, fixes #193
- function `ValueTask.FromResult` is renamed to `ValueTask.fromResult`, fixes #193
0.4.0-alpha.1
- bugfix: not calling Dispose for 'use!', 'use', or `finally` blocks #157 (by @bartelink)
- BREAKING CHANGE: null args now raise ArgumentNullException instead of NullReferenceException, #127
- adds `let!` and `do!` support for F#'s Async<'T>, #79, #114
- adds TaskSeq.takeWhile, takeWhileAsync, takeWhileInclusive, takeWhileInclusiveAsync, #126 (by @bartelink)
- adds AsyncSeq vs TaskSeq comparison chart, #131
- bugfix: removes release-notes.txt from file dependencies, but keep in the package, #138
0.3.0
- improved xml doc comments, signature files for exposing types, fixes #112.
- adds support for static TaskLike, allowing the same let! and do! overloads that F# task supports, fixes #110.
- implements 'do!' for non-generic Task like with Task.Delay, fixes #43.
- task and async CEs extended with support for 'for .. in ..do' with TaskSeq, #75, #93, #99 (in part by @theangrybyrd).
- adds TaskSeq.singleton, #90 (by @gusty).
- bugfix: fixes overload resolution bug with 'use' and 'use!', #97 (thanks @peterfaria).
- improves TaskSeq.empty by not relying on resumable state, #89 (by @gusty).
- bugfix: does not throw exceptions anymore for unequal lengths in TaskSeq.zip, fixes #32.
- BACKWARD INCOMPATIBILITY: several internal-only types now hidden
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