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" />
paket add FSharp.Control.TaskSeq --version 0.4.0
#r "nuget: FSharp.Control.TaskSeq, 0.4.0"
// Install FSharp.Control.TaskSeq as a Cake Addin #addin nuget:?package=FSharp.Control.TaskSeq&version=0.4.0 // Install FSharp.Control.TaskSeq as a Cake Tool #tool nuget:?package=FSharp.Control.TaskSeq&version=0.4.0
TaskSeq
IAsyncEnumerableTaskSeq
模块和函数的 taskSeq { ... }
,允许无缝使用与 F# 原生 seq
和 task
CE 类似的异步序列。
此README介绍了一些重点和已实现函数的摘要。更详细的内容可以在存储库的README中找到。
目录
概述
.NET 在 .NET Core 3.0
中添加了 IAsyncEnumerable
接口,它是 .NET Standard 2.1
的一部分。主要用例是针对某些资源的迭代异步、顺序枚举。例如,一个事件流或分页的 REST API 接口,或异步读取文件列表并累积结果,其中的每个动作可以建模为对由 MoveNextAsync
调用返回的 IAsyncEnumerator<'T>
的调用,而该调用是通过 GetAsyncEnumerator()
调用获得的。
自 F# 中引入 task
以来,对原生 任务序列 实现的需求不断增长,特别是在需要对 IAsyncEnumerable
进行正确迭代的情况下,尤其是当想要避免使用可变变量时。这个库是对这一需求的回应,并使用 taskSeq
实现了相同的 可恢复状态机 方法。
模块函数
与 seq
和 Seq
一样,这个库提供了一堆熟悉收集函数,如 TaskSeq.empty
、isEmpty
或 TaskSeq.map
、iter
、collect
、fold
和 TaskSeq.find
、pick
、choose
、filter
。在这些函数适用的情况下,它们还包括异步版本,如 TaskSeq.mapAsync
、iterAsync
、collectAsync
、foldAsync
和 TaskSeq.findAsync
、pickAsync
、chooseAsync
、filterAsync
,这使得应用的功能可以是异步的。
有关当前实现的函数及其变体的完整列表,请参阅下文。
taskSeq
计算表达式
可以使用 taskSeq
计算表达式就像使用 seq
一样。除此之外,它还添加了对通过 let!
和循环遍历正常或异步序列(实现 IAsyncEnumerable<'T>
的序列)进行处理的支持。您可以使用 yield!
和 yield
,并且对任务序列表达式内的 use
和 use!
、try-with
和 try-finally
以及 while
循环提供了支持。
示例
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
模块函数
我们正在努力在 TaskSeq
上得到一组完整的模块函数,以便与 IAsyncEnumerable
序列一起使用。我们的指导是 F# 核心中 Seq
函数的集合,以及适用时 AsyncSeq
提供的函数。每个实现的函数都通过 XML 文档注释进行了文档化,以提供必要的上下文相关的帮助。
以下是至今为止已实现、计划实现或跳过的内容。
已完成 | Seq |
TaskSeq |
变体 | 备注 |
---|---|---|---|---|
❓ | allPairs |
allPairs |
笔记 #1 | |
✅ #81 | append |
append |
||
✅ #81 | appendSeq |
|||
✅ #81 | prependSeq |
|||
average |
average |
|||
averageBy |
averageBy |
averageByAsync |
||
❓ | cache |
cache |
笔记 #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 (列表) |
concat (列表) |
||
✅ #237 | concat (数组) |
concat (数组) |
||
✅ #237 | concat (r-array) |
concat (r-array) |
||
✅ #237 | concat (seq) |
concat (seq) |
||
✅ #70 | contains |
contains |
||
✅ #82 | 延迟 |
延迟 |
||
distinct |
distinct |
|||
distinctBy |
dictinctBy (此处有一个拼写错误,可能为distinctBy) |
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 |
笔记 #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 |
笔记 #1 | ||
scan |
scan |
scanAsync |
||
🚫 | scanBack |
note #2 | ||
✅ #90 | singleton |
singleton |
||
✅ #209 | skip |
skip |
||
✅ #219 | skipWhile |
skipWhile |
skipWhileAsync |
|
✅ #219 | skipWhileInclusive |
skipWhileInclusiveAsync |
||
❓ | sort |
笔记 #1 | ||
❓ | sortBy |
笔记 #1 | ||
❓ | sortByAscending |
笔记 #1 | ||
❓ | sortByDescending |
笔记 #1 | ||
❓ | sortWith |
笔记 #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 |
笔记 #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
这些函数要求通过 TaskSeq.cache
进行某种形式的预先验证,类似于在相应的 Seq
函数中采用的方法。拥有一个缓存的后台异步序列没有太多意义。但是,AsyncSeq
仍然实现了这些功能,所以我们可能会最终这样做。
备注2
由于 TaskSeq
序列的异步本质,从后往前迭代是不好的做法。相反,应将序列材料化为列表或数组,然后应用 xxxBack
迭代器。
备注3
在 Seq
中引入 readOnly
的动机是,从可变的数组或列表到 seq<_>
的转换是有效的,并且可以转换回,从而导致可变序列。由于 TaskSeq
不实现 IEnumerable<_>
,因此这些转换是不可能的。
更多信息
进一步阅读 IAsyncEnumerable
- 有关如何使用 C# 的良好介绍,请参阅此博客 https://stu.dev/iasyncenumerable-introduction/。
- 在此之后不久,《MSDN 文章》介绍了异步可枚举。
- 以下是一个示例,说明如何将
seq
转换为IAsyncEnumerable
https://gist.github.com/akhansari/d88812b742aa6be1c35b4f46bd9f8532,尽管TaskSeq
包含许多更多实用功能,且采用的方法略有不同。 - 如果您想使用
IAsyncEnumerable
并使用async
而不是task
,则应使用出色的AsyncSeq
库。虽然TaskSeq
的目的是像task
一样消费async
,但它不会创建一个AsyncSeq
类型(至少目前还没有)。如果您想使用经典异步和并行处理,则应使用此库。
关于可恢复状态机的进一步阅读
- F# 单子视角下的状态机 https://blumu.github.io/ResumableMonad/TheResumableMonad.html,它与 F# 6.0 之前的不可恢复内部结构一起使用。
- F# 6.0 上可恢复状态机的原始 RFC https://github.com/fsharp/fslang-design/blob/main/FSharp-6.0/FS-1087-resumable-code.md。
- 引入 F# 6.0 中
task
的原始 RFC https://github.com/fsharp/fslang-design/blob/main/FSharp-6.0/FS-1097-task-builder.md。 - 在 F# Core 中后来添加的
task
CE 动机的前 F# 6.0TaskBuilder
https://github.com/rspeele/TaskBuilder.fs/。 - 有关
task
https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/task-expressions 和async
https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/async-expressions 的 MSDN 文档。
关于计算表达式的进一步阅读
- MSDN 上的 https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/computation-expressions 文档构成了一个好的总结和起点。
- Scott Wlaschin 的逐步教程是目前有争议的最好的 使用和构建计算表达式的教程。
产品 | 版本 兼容的和额外的计算目标框架版本。 |
---|---|
.NET | net5.0 已计算。 net5.0-windows 已计算。 net6.0 已计算。 net6.0-android 已计算。 net6.0-ios 已计算。 net6.0-maccatalyst 已计算。 net6.0-macos 已计算。 net6.0-tvos 已计算。 net6.0-windows 已计算。 net7.0 已计算。 net7.0-android 已计算。 net7.0-ios 已计算。 net7.0-maccatalyst 已计算。 net7.0-macos 已计算。 net7.0-tvos 已计算。 net7.0-windows 已计算。 net8.0 已计算。 net8.0-android 已计算。 net8.0-browser 已计算。 net8.0-ios 已计算。 net8.0-maccatalyst 已计算。 net8.0-macos 已计算。 net8.0-tvos 已计算。 net8.0-windows 已计算。 |
.NET Core | netcoreapp3.0 已计算。 netcoreapp3.1 已计算。 |
.NET Standard | netstandard2.1 兼容。 |
MonoAndroid | monoandroid 已计算。 |
MonoMac | monomac 已计算。 |
MonoTouch | monotouch 已计算。 |
Tizen | tizen60 已计算。 |
Xamarin.iOS | xamarinios 已计算。 |
Xamarin.Mac | xamarinmac 已计算。 |
Xamarin.TVOS | xamarintvos 已计算。 |
Xamarin.WatchOS | xamarinwatchos 已计算。 |
-
.NETStandard 2.1
- FSharp.Core (>= 6.0.1)
NuGet包 (10)
显示依赖FSharp.Control.TaskSeq的前5个NuGet包
包 | 下载 |
---|---|
Equinox.CosmosStore
高效的源事件决策和数据 |
|
Equinox.EventStore
高效的源事件决策和数据 |
|
Propulsion.Feed
高效的事件流管道 |
|
Equinox.DynamoStore
高效的源事件决策和数据 |
|
AzureTackle
AzureTackle的F# API,实现高效访问AzureTackle数据库,并添加了函数式调味。 |
GitHub存储库
此包未在任何流行的GitHub存储库中使用。
发行说明
0.4.0
- 重构所有文档注释,添加异常,改善IDE快速信息体验,#136,#220,#234
- 新颖的表面函数,修复 #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 (覆盖:seq, array, resizearray, list), #237
- 性能:用 'StartImmediateAsTask' 替代 'StartAsTask' 以减少线程跳跃,修复 #135
- 性能:几个内联和分配改进
- BINARY INCOMPATIBILITY:'TaskSeq' 模块被 'TaskSeq<_>' 上的静态成员替换,修复 #184
- DEPRECATIONS(警告 FS0044)
- 类型 'taskSeq<_>' 重命名为 'TaskSeq<_>', 修复 #193
- 函数 'ValueTask.ofIValueTaskSource` 重命名为 `ValueTask.ofSource', 修复 #193
- 函数 `ValueTask.FromResult` 重命名为 `ValueTask.fromResult', 修复 #193
0.4.0-alpha.1
- bugfix:对于 'use!'、'use' 或 `finally` 块,不调用 Dispose,#157(由 @bartelink)
- BREAKING CHANGE:null 参数现在引发 ArgumentNullException,而不是 NullReferenceException,#127
- 添加对F#的 Async<'T> 的 `let!` 和 `do!` 支持,#79,#114
- 添加 TaskSeq.takeWhile, takeWhileAsync, takeWhileInclusive, takeWhileInclusiveAsync,#126(由 @bartelink)
- 添加 AsyncSeq与TaskSeq比较图,#131
- bugfix:从文件依赖中删除 release-notes.txt,但保留在包中,#138
0.3.0
- 改进xml文档注释,命名文件以公开类型,修复 #112。
- 添加对静态 TaskLike 的支持,允许使用与F#任务相同的 let! 和 do! 载重,修复 #110。
- 实现 'do!' 以支持非泛型的 Task,如 Task.Delay,修复 #43。
- task和async CEs用对 'for .. in ..do' 的支持扩展,#75,#93,#99(部分由 @theangrybyrd)
- 添加 TaskSeq.singleton,#90(由 @gusty)
- bugfix:修复与 'use' 和 'use!' 相关的重载解析错误,#97(感谢 @peterfaria)
- 改进 TaskSeq.empty,不依赖于可恢复状态,#89(由 @gusty)
- 修复了 bug:在 TaskSeq.zip 中不再因为长度不等而抛出异常,修复了 #32 问题。
- 兼容性退步:几个仅内部使用类型现在已隐藏
0.2.2
- 删除了错误命名的 TaskSeq.toSeqCachedAsync。请使用 toSeq 或 toListAsync。
- 将 TaskSeq.toSeqCached 重命名为 TaskSeq.toSeq,这是其实际的工作行为。
0.2.1
- 修复了完成迭代中的 ValueTask 的问题。
- 添加了 `TaskSeq.except` 和 `TaskSeq.exceptOfSeq` 异步操作。
0.2
- 从 .NET 6.0 移动到 NetStandard 2.1 以实现更大的兼容性,无功能变更。
- 移动到最小必要的 FSharp.Core 版本:6.0.2。
- 更新了README中的进度概述,纠正了元信息,并添加了发布说明。
0.1.1
- 更新了 nuggest 包中的元信息并添加了 README。
0.1
- 首次发布
- 使用可恢复的状态机实现 taskSeq CE
- 支持:yield,yield!,let,let!,while,for,try-with,try-finally,use,use!
- 以及:任务和值任务
- 添加了 toXXX / ofXXX 函数
- 添加了 map/mapi/fold/iter/iteri/collect 等,并带有异步版本
- 添加了 find/pick/choose/filter 等,并带有异步版本和 'try' 变体
- 添加了 cast/concat/append/prepend/delay/exactlyOne
- 添加了 empty/isEmpty
- 添加了 findIndex/indexed/init/initInfinite
- 添加了 head/last/tryHead/tryLast/tail/tryTail
- 添加了 zip/length