Jamie Dixon
Jamie Dixon

Reputation: 4282

F# async and anonymous functions

I have a method that does some IO with this signature:

member this.IsRestaurantInCategoryAsync(restaurantName: string, restaurantAddress: string, restaurantCategory: string) =
    async { ///long running methods }

I want to invoke it in an anonymous function like so:

this.GetRestaurants()
        |> Seq.filter(fun (name, address) -> categoryRepository.IsRestaurantInCategoryAsync(name, address, category))
        |> Seq.toList

The problem is that the IsRestaurantInCategoryAsync returns a async<bool>, not a bool. How do I have the Seq.Filter handle it?

Should I cast the async<bool> to a bool using a let! so then I have to write a non-anonymous function to assign the return?

Upvotes: 3

Views: 504

Answers (2)

Jim G.
Jim G.

Reputation: 15365

Use FSharp.Control.AsyncSeq:

this.GetRestaurants()
        |> AsyncSeq.filterAsync categoryRepository.IsRestaurantInCategoryAsync name address category
        |> AsyncSeq.toListAsync

Upvotes: 0

Tomas Petricek
Tomas Petricek

Reputation: 243041

You could use Async.RunSynchronously to run the operation synchronously - but that would defeat the point of using asynchronous workflows to avoid writing blocking code, so that is not the right way to go!

There are different ways to do it - you could iterate over all restaurants sequentially (which will handle one by one) or you could run the filtering in parallel (which will use as many threadpool threads as .NET finds good).

The parallel version looks like this:

let checkAll = async {
  let! all =  
    [ for r in this.GetRestaurants() -> async {
        let! include = catagoryRepository.IsRestaurantInCatagoryAsync(name, address,catagory) 
        if include then return Some(r) else return None } ]
    |> Async.Parallel
  let included = Seq.choose id all 
  printfn "%A" included }

Note that the code is all inside async block (because this keeps it asynchronous). It first creates a list of computations that return options with None (to skip restaurant) or Some (to include restaurant), then runs all of them and filters the None values using Seq.choose.

To implement this sequentially, you'd basically need your own implementation of filter that is wrapped in async blocks. This would be a good starting point (although it is not tail-recursive):

let rec filterAsync f items = async {
  match items with
  | [] -> return []
  | x::xs -> 
      let! included = f x
      let! rest = filterAsync f xs
      return (if included then x::rest else rest) }

Upvotes: 2

Related Questions