Using async methods in one branch of match

Viewed 241

I'm getting started with F# and async.

I have trying to convert some C# code that looks like:

public async Task<string> getValueOrDefault(string? key)
{
  if (key is null)
  {
    return "default";
  }
  else
  {
    return await getValue(key);
  }
}

My F# code looks like:

    let getValueOrDefault (key: Option<string>) =
        async {
            return match key with
            | None -> "default"
            | Some (key) -> getValue key
        }

It complains that the 2 branches have different types: string and Async<string>. Changing return to return! of course tells me that "default" is expected to be Async<'a>

I can do:

    let getValueOrDefault (key: Option<string>) =
        async {
            return! match key with
                    | None -> async { return "default" }
                    | Some (key) -> getValue key
        }

But is this the "right" way of doing it? Does it matter?

3 Answers

You're missing the equivalent of the await keyword in your first F# version which is let! or return!.

I think your second version is perfectly adequate, but you can also do this, where each of the match results returns its own value.

    let getValueOrDefault (key: Option<string>) =
        async {
            match key with
            | None -> return "default"
            | Some (key) -> return! getValue key
        }

I would probably write this the way Tim does in his answer, i.e. wrap the whole method in an async { .. } block and then use return in one branch and return! in the other. However, you can also simplify your last snippet a bit by turning async { return! e } to just e - because that's exactly equivalent.

Then you get another quite elegant version, which also encodes the same logic:

let getValueOrDefault (key: Option<string>) =
    match key with
    | None -> async { return "default" }
    | Some (key) -> getValue key

There's nothing really wrong with your attempt, except that the double computational expressions look a bit weird. You can easily avoid that by calling getValue outside your async {}

How about this:

let getValueOrDefault (key:string option) =
    key 
    |> Option.map getValue
    |> Option.defaultValue (async { return "default"})
Related