4

So I currently have a sequence of type seq<System.Threading.Tasks.Task<Restaurant>> and I want to turn it into a sequence of type seq<Restaurant>.

I'm currently using TaskBuilder.fs library and from my research, I need to use either let! or do! for this situation but they require task {} which when used with Seq.map bring back the same Task type.

let joinWithReviews (r : Restaurant) =
    task {
        let! reviewResult = Reviews.Database.getByLocationId cnf.connectionString r.Restaurant_Id
        match reviewResult with
        | Ok reviewResult ->
            let restaurant = { r with Reviews = (List.ofSeq reviewResult)}
            return restaurant
        | Error ex ->
            return raise ex
    }

let indexAction (ctx : HttpContext) =
    task {
        let (cnf:Config) = Controller.getConfig ctx
        let! result = Restaurants.Database.getAll cnf.connectionString
        match result with
        | Ok result ->
            let restaurantWithReviews = (Seq.map joinWithReviews result)
            return index ctx (List.ofSeq restaurantWithReviews)
        | Error ex ->
            return raise ex
    }

So my result is of type Seq<Restaurant> and I need to add reviews to each restaurant so I use Seq.map to get restaurantWithReviews which is type seq<System.Threading.Tasks.Task<Restaurant>> which I won't be able to use.

Danson
  • 415
  • 1
  • 7
  • 16
  • Are you trying to do some of the processing in parallel or not? – Tomas Petricek Mar 29 '19 at 13:50
  • I don't need to process it in parallel but my database calls are of type Task and the framework I am using required the same of my IndexAction. – Danson Mar 29 '19 at 15:18
  • To turn a `seq>` into a `seq`, the easiest way would be a simple `|> Seq.map (fun task -> task.Result)`. The [MSDN docs](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task-1.result?view=netframework-4.7.2#System_Threading_Tasks_Task_1_Result) say that accessing the `Result` property of a Task will block the thread until the result is available, and do note that this would be sequential, not parallel. But it's definitely the easiest and simplest way to do what you need. – rmunn Mar 29 '19 at 19:07
  • Thank! That is enough to serve my needs. – Danson Mar 30 '19 at 05:25
  • What if i want to run `Seq.map` sequentially without having to block the thread with calling `task.Result`? – David Spiess Oct 08 '20 at 19:24

1 Answers1

3

The .NET method System.Threading.Tasks.Task.WhenAll will convert seq<Task<'a>> to Task<'a[]>. You can get the result with let! if you're inside a task { } block.

let restaurants: seq<Restaurant>

let! withReviews: Restaurant[] =
    restaurants
    |> Seq.map joinWithReviews
    |> Task.WhenAll
libertyernie
  • 2,590
  • 1
  • 17
  • 13
  • Thank you! That does the trick. The type are a bit off bit it is easy enough to switch between them. – Danson Mar 30 '19 at 05:29
  • This saved me so much today, thank you! – Kanagawa Marcos Jul 25 '22 at 22:42
  • 1
    Note that this solution will return an eagerly evaluated array. If you want to maintain the `seq` behavior, FSharpPlus has a function `SeqT.run` that can maintain the sequence-like behavior while evaluating the tasks. I.e., evaluating the first 2 items, will not start the tasks of the next items. – Abel Sep 28 '22 at 23:27