Search code examples
asynchronousf#computation-expressionsuave

return position in an async block, in F#


I have the following code:

let private SendOk (payload: 'a) : WebPart =
    payload |> Json.serialize |> OK >=> Writers.setMimeType "application/json"


let getBTCBalance (addresses: string list) : Async<WebPart> =
    async {
        try
            let! reply = blockExplorer.GetMultiAddressAsync addresses |> Async.AwaitTask
            return reply.Addresses |> Seq.map (fun x -> x.FinalBalance.GetBtc()) |> SendOk
        with
        | :? ArgumentNullException as ex -> return BAD_REQUEST    ex.Message
        | ex                             -> return INTERNAL_ERROR ex.Message
    }

It's using the Suave web server, and both paths (ok and errors) return an object of type WebPart.

If I try to move the return statement to wrap everything else, I get the following code:

let getBTCBalance (addresses: string list) : Async<WebPart> =
    async {
        return ( 
            try
                let! reply = blockExplorer.GetMultiAddressAsync addresses |> Async.AwaitTask
                reply.Addresses |> Seq.map (fun x -> x.FinalBalance.GetBtc()) |> SendOk
            with
            | :? ArgumentNullException as ex -> BAD_REQUEST    ex.Message
            | ex                             -> INTERNAL_ERROR ex.Message
        )
    }

The try/with block should return a WebPart object and I thought it would read better by having a single return wrapping it all.

The compiler doesn't agree:

[FS0750] This construct may only be used within computation expressions

I do not understand why this happens. Can anyone explain it to me?


Solution

  • That's because you're using let!.

    let! creates a breaking point in the async computation, splitting the code in two, with second part passed as a continuation (see this answer for how that works).

    You can't return the whole thing as a single expression, because it's not, in fact, a single expression, it only looks like one. In reality, your function ends after let! and a totally separate function begins.

    Here's a simplified version of your problem:

    let f = async {
      return (let! x = doSomethingAsync)
    }
    

    Does that give you a better idea of what's wrong?