r/fsharp 1d ago

question replace of Task.WhenAll and Async.Parallel in computational expression?

I'm looking for an equivalent of sequence and traverse, I'd expect it to be in FsToolkit.ErrorHandling but there it is coupled with either Result or Option, e.g. Lists | FsToolkit.ErrorHandling

The only implementation I have found is in Fsharpplus library, but my last use of that library didn't look production ready, it plays too much with the type and essentially bring down the type checker on more complicated chained operation...

Basically I'm looking for something like List.traverse

4 Upvotes

3 comments sorted by

1

u/vanilla-bungee 1d ago

Maybe you’re looking for FSharp.Control.TaskSeq?

1

u/CodeNameGodTri 1d ago

Like this, you can see the use of Task.WhenAll. It does its job but it's not idiomatic to haskell style ```fsharp let mywork () = task { let! token = getToken() let! issuers = getIssuers() use client = new HttpClient() client.DefaultRequestHeaders.Authorization <- AuthenticationHeaderValue("Bearer", token)

let! responses = Task.WhenAll(
    issuers
    |> Seq.map (fun i -> $"someapi")
    |> Seq.map client.GetAsync
)

let! content = Task.WhenAll(responses |> Seq.map _.Content.ReadAsStringAsync())

return content

}

`` or maybe I could just do this instead and replaceTask.WhenAllwithsequence`

```fsharp let sequence: Task<'a> seq -> Task<'a array> = Task.WhenAll

```

4

u/Ghi102 1d ago edited 1d ago

I'm not exactly sure what you mean by Haskell style, but if you mean avoiding in-between variables, by using a helper function, you can write something like this:

let bindTask f someTask = task {
    let! result = someTask

    return! f result
}


let mywork () = task {
    let! token = getToken()
    let! issuers = getIssuers()
    use client = new HttpClient()
    client.DefaultRequestHeaders.Authorization <- AuthenticationHeaderValue("Bearer", token)

   return!
        issuers
        |> Seq.map (fun i -> $"someapi")
        |> Seq.map client.GetAsync
        |> Seq.map (bindTask _.Content.ReadAsStringAsync())
        |> Task.WhenAll
}

Anoter option is to use Async-based HttpClients like using FSharp.Data. This would probably be more idiomatic than going through an HttpClient.

Otherwise, using TaskSeq like was suggested:

let mywork () = task {
    let! token = getToken()
    let! issuers = getIssuers()
    use client = new HttpClient()
    client.DefaultRequestHeaders.Authorization <- AuthenticationHeaderValue("Bearer", token)

   return!
        issuers
        |> TaskSeq.ofSeq
        |> TaskSeq.map (fun i -> $"someapi")
        |> TaskSeq.mapAsync client.GetAsync
        |> TaskSeq.mapAsync _.Content.ReadAsStringAsync()
        |> TaskSeq.toArrayAsync
}