F#异步while循环积累的惯用方式转换

5

什么是在F#中处理异步while循环累加的惯用方式?

我正在使用新的(仍处于预览阶段)Azure Cosmos DB SDK。查询数据库返回一个 CosmosResultSetIterator<T>,其中具有一个 HasMoreResults 属性和一个 FetchNextSetAsync() 方法。我的直译C#代码如下:

let private fetchItemsFromResultSet (resultSetIterator: CosmosResultSetIterator<'a>) =
    let results = ResizeArray<'a>()
    async {
        while resultSetIterator.HasMoreResults do
            let! response = resultSetIterator.FetchNextSetAsync() |> Async.AwaitTask
            results.AddRange(response |> Seq.toArray)

        return Seq.toList results
    }

小建议:我会将 let results 移到异步块中,因为我猜这是你想要的? - Just another metaprogrammer
3个回答

6
我建议您查看AsyncSeq包。您可以使用它创建异步计算序列,然后在异步或并行方式下迭代它们。这允许异步绑定在序列内部,并且yield可以异步发生,因此您不必显式地构建累加器。
您可以使用它来执行以下操作:
open FSharp.Control

let private fetchItemsFromResultSet (resultSetIterator: CosmosResultSetIterator<'a>) =
    asyncSeq {
        while resultSetIterator.HasMoreResults do
            let! response = resultSetIterator.FetchNextSetAsync() |> Async.AwaitTask
            yield! response |> AsyncSeq.ofSeq
    }

这看起来非常有趣,谢谢。不过我有一个问题,在上面的代码中,response本身就是一个序列,我需要返回response中的每个项目,所以我认为yield response不会起作用。我似乎需要一些嵌套的yield。 - Brian Vallelunga
@BrianVallelunga 哦,那很容易。只需将其设置为 yield! 即可展平序列。我已更新答案。 - Aaron M. Eshbach
谢谢!我尝试了一下,它运行得相当不错。不过我不得不做一个小改变,因为响应不能直接兼容 AsyncSeq。最后一行我改成了 yield!response |> AsyncSeq.ofSeq - Brian Vallelunga

2
最近,FSharp.Control.TaskSeq 已被添加以原生支持 seqs 中的 tasks。@Just another metaprogrammer 的回答 此处 可以重写为

#r "nuget: FSharp.Control.TaskSeq"

open FSharp.Control

let private fetchItemsFromResultSet (resultSetIterator: CosmosResultSetIterator<'a>) = taskSeq {
    while resultSetIterator.HasMoreResults do
        let! response = resultSetIterator.FetchNextSetAsync()
        yield! response |> TaskSeq.ofSeq
}

现在看起来也很棒,谢谢。 - Brian Vallelunga

2

在我看来,尾递归比while循环更可取,因为它是避免突变的一种方式。

例如:

Original Answer翻译成"最初的回答"

let fetchItemsFromResultSet (resultSetIterator: CosmosResultSetIterator<'a>) =
  let rec loop results =
    async {
      if resultSetIterator.HasMoreResults then
        let! vs = resultSetIterator.FetchNextSetAsync () |> Async.AwaitTask
        let vs = vs |> Seq.toList
        return! loop (vs::results)
      else
        // List.rev needed because batches are in reverse
        return results |> List.rev |> List.concat
    }
  loop []

2
这是我在没有 AsyncSeq 的情况下采取的方法。 - Brian Vallelunga
这不是尾递归,即使看起来像。请参见:https://github.com/dotnet/fsharp/issues/12749 - Tuomas Hietanen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接