Question

I'm writing an asynchronous HTTP API client module/library. To make everything as DRY as possible, I'm trying to compose every HTTP API call from separate parts that make an API call, bottom-up: building a request, getting a response, reading a response into string buffer, parsing JSON contents of that string buffer into an object.

So far I have this code:

module ApiUtils =
    // ... request builder fns omitted ...

    let getResponse<'a> (request : Net.WebRequest) = 
        request.AsyncGetResponse()

    let readResponse (response : Net.WebResponse) =
        use reader = new StreamReader(response.GetResponseStream())
        reader.AsyncReadToEnd()

    let getString = getResponse >> (Async.flatMap readResponse)

    let parseJson<'T> responseText : 'T =
        Json.JsonConvert.DeserializeObject<'T> responseText

    let getJson<'T> = getString >> (Async.map parseJson<'T>)

And, as you can see, I've extended the Async module with my own additions:

module Async =
    let map f m =
        async {
            let! v = m
            return f v
        }

    let flatMap f m =
        async {
            let! v = m
            return! f v
        }

The goal I am trying to achieve is to build a module with functions that I can use in async blocks, to take all the advantage of computation expression syntax. I was wondering if I am doing it right, if I'm choosing the right names, etc. I have very little to no formal functional programming education, and sometimes I'm not even sure I know what I'm doing.

Was it helpful?

Solution

When writing asynchronous code in F# , I find it easier to just use the built-in computation asynchronous workflow syntax rather than trying to build more sophisticated combinators.

In your example, you would not really duplicate any code if you just write a straightforward function, so I think the following does not break the DRY principle and it is considerably simpler (and it is also easy to extend the code to handle exceptions, which would be otherwise difficult):

let getJson<'T> (request:Net.WebRequest) = async { 
  let! response = request.AsyncGetResponse()
  use reader = new StreamReader(response.GetResponseStream())
  let! data = reader.AsyncReadToEnd()
  return Json.JsonConvert.DeserializeObject<'T> data }

Of course, you could split the code into downloadData and getJson if you need to download data for other purposes elsewhere in the code.

In general, when writing functional code, you have two options how to compose computations:

  • Using the language syntax (such as loops, let and try .. with, use in both plain F# and in asynchronous workflows). This approach generally works well if you're writing some computation, because the language is designed to describe computations and can do that well.

  • Using custom combinators (such as map, >> and |> or library-specific operators). This is needed if you're modeling something that is not just a computation, for example an interactive animation, a stock option, a user interface component or a parser. However, I would only follow this path if the basic features of the language are not sufficient to express the problem.

Aside, you might be interested in the F# Data library, which implements helpers for various tasks including HTTP requests, JSON parsing and JSON type provider, which might make your life even easier.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top