SimonF
SimonF

Reputation: 824

Combine Async and Option monads

In writing some code that works with a lot of nested async workflows lately I've found a pattern emerging that smells to me. A simple example:

let flip f x y = f y x
let slowInc x = async {
    do! Async.Sleep 500
    printfn "Here you go, %d" x
}

let verboseFun inp = async {
    match List.tryFind (flip (>) 3) inp with
    | Some x -> do! slowInc x
    | _ -> ()
}

verboseFun [1..5] |> Async.RunSynchronously

The 'verboseFun' to me seems verbose but I can't think of a way to combine the Option and Async monads so it can be rewritten without the pattern match. I was thinking something like

let terseFun inp = async {
    inp
    |> List.tryFind (flip (>) 3)
    |> Option.iterAsync slowInc
}

It just seemed to me that it's highly likely I just don't know what building blocks are available to achieve this.

EDIT: Extra clarification after Tomas' answer.

I was trying to adapt what would be trivial to me if everything was synchronous, e.g.,

let terseFun inp =
    inp
    |> List.tryFind (flip (>) 3)
    |> Option.iter someSideEffectFunciton

to become part of nested async workflows. Originally I was thinking "just chuck a do! in there" so came up with

let terseFun inp = async {
    inp
    |> List.tryFind (flip (>) 3)
    |> Option.iter (fun x -> async { do! someSideEffectFunciton x })
    |> ignore
}

But it immediately smelled wrong to me because VS started demanding the ignore. Hope this helps clarify.

Upvotes: 1

Views: 776

Answers (1)

Tomas Petricek
Tomas Petricek

Reputation: 243061

The ExtCore library has a bunch of helper functions that let you work with asynchronous computations that return optional values, i.e. of type Async<'T option> and it even defines asyncMaybe computation builder for working with them.

I have not used it extensively, but from a few simple experiments I did, it looks like it is not as nicely integrated with the rest of F#'s async functionality as it perhaps could be, but if you want to go in this direction, ExtCore is probably the best library around.

The following is using the iter function from AsyncMaybe.Array (source is here). It is a bit ugly, because I had to make slowInc be of type Async<unit option>, but it is pretty close to what you asked for:

let slowInc x = async {
    do! Async.Sleep 500
    printfn "Here you go, %d" x
    return Some ()
}

let verboseFun inp = 
  inp 
  |> List.tryFind (fun x -> 3 > x) 
  |> Array.ofSeq
  |> AsyncMaybe.Array.iter slowInc 
  |> Async.Ignore

Aside, I also removed your flip function, because this is not generally recommended style in F# (it tends to make code cryptic).

That said, I think you don't really need an entire ExtCore library. It is hard to see what is your general pattern from just one example you posted, but if all your code snippets look similar to the one you posted, you can just define your own asyncIter function and then use it elsewhere:

let asyncIter f inp = async {
  match inp with 
  | None -> ()
  | Some v -> do! f v }

let verboseFun inp = 
   inp 
   |> List.tryFind (fun x -> x > 3) 
   |> asyncIter slowInc

The great thing about F# is that it is really easy to write these abstractions yourself and make them so that they exactly match your needs :-)

Upvotes: 2

Related Questions