21

Say i want to return an Option while in an async workflow:

let run = 
    async {
        let! x = doAsyncThing
        let! y = doNextAsyncThing x
        match y with
        | None -> return None
        | Some z -> return Some <| f z
    }

Ideally I would use the maybe computation expression from FSharpx at the same time as async to avoid doing the match. I could make a custom builder, but is there a way to generically combine two computation expressions? It might look something like this:

let run = 
    async {
        let! x = doAsyncThing
        let! y = doNextAsyncThing x
        return! f y
    }
Gus
  • 25,839
  • 2
  • 51
  • 76
Geoff
  • 1,634
  • 16
  • 25
  • 6
    I think you're asking for what Haskell calls "monad transformers", in which case the short answer is "no". There's no generic way to combine them, unfortunately. In Haskell there's a transformer version of each standard monad that you can then graft onto others, but I'm not sure it's even possible to define those in F#'s type system... – C. A. McCann Sep 11 '12 at 21:22
  • You might be interested in an approved proposed feature called [match!](https://github.com/fsharp/fslang-suggestions/issues/572) (as in "match-bang"): it's a "let!" followed by a "match". I've wanted something to do what you're talking about for a little while now, and I've been working on getting it implemented into F# – Jwosty Mar 04 '18 at 04:06

3 Answers3

23

Typically in F# instead of using generic workflows you define the workflow by hand, or use one that is ready available as in your case async and maybe but if you want to use them combined you will need to code a specific workflow combination by hand.

Alternatively you can use F#+ which is a project that provides generic workflows for monads, in that case it will be automatically derived for you, here's a working example, using your workflow and then using OptionT which is a monad transformer:

#r "nuget: FSharpPlus, 1.2"

open FSharpPlus
open FSharpPlus.Data

let doAsyncThing = async {return System.DateTime.Now}
let doNextAsyncThing (x:System.DateTime) = async {
    let m = x.Millisecond  
    return (if m < 500 then Some m else None)}
let f x = 2 * x

// then you can use Async<_> (same as your code)
let run = monad {
    let! x = doAsyncThing
    let! y = doNextAsyncThing x
    match y with
    | None   -> return None
    | Some z -> return Some <| f z}

let res = Async.RunSynchronously run

// or you can use OptionT<Async<_>> (monad transformer)

let run' = monad {
    let! x = lift doAsyncThing
    let! y = OptionT (doNextAsyncThing x)
    return f y}

let res' = run' |> OptionT.run |> Async.RunSynchronously

The first function has to be 'lifted' into the other monad, because it only deals with Async (not with Option), the second function deals with both so it only needs to be 'packed' into our OptionT DU.

As you can see both workflows are derived automatically, the one you had (the async workflow) and the one you want.

For more information about this approach read about Monad Transformers.

Gus
  • 25,839
  • 2
  • 51
  • 76
7

A simple way to do so is to use Option module:

let run = 
    async {
        let! x = doAsyncThing
        let! y = doNextAsyncThing x
        return Option.map f y
    }

I suppose you don't have to deal with option in context of async so often. FSharpx also provides many more high-order functions for option type. Most of the cases, I think using them is enough.

To get the feeling of using these functions, please take a look at this nice article.

pad
  • 41,040
  • 7
  • 92
  • 166
2
type MaybeMonad() = 
    member __.Bind(x, f) = 
        match x with
        | Some v -> f v
        | None -> None
    member __.Return(x) = 
        Some x

let maybe = MaybeMonad()

let run = async {
    let! x = doAsyncThing
    let! y = doNextAsyncThing x
    return maybe {
        let! y_val = y
        return f y_val
    }
}

just use f# Computation expressions inside.