Futures in F#

My first exposure to the concept of futures (or promises) was probably JavaScript's Q API and AngularJS' $q service, but it was only after reading Twitter's "Your Server as a Function" paper that their usefulness really sank in. The closest thing that .Net has to the Scala futures illustrated in the paper are Task<T> and F#'s Async<'T>. Although these types are useful they do not provide all of the functionality of Scala's Future[T], nor are they as elegant.

In this post I will describe an approach to creating something akin to Scala's Future[T] in F#.

Defining a future

A broad definition of a future would be an asynchronous computation which may or may not succeed. As this post is about F# we will base our solution on the Async<'T> type (Async.AwaitTask function can be used to convert a Task<T> to Async<'T> if required).

We can define a discriminated union to represent the result of a future:

type Outcome<'a> = 
    | Success of 'a
    | Failure of Exception

Using this we can define a future as:

type Future<'a> = Async<Outcome<'a>>

Creating a future

Now that we have a definition of a future, we can think about ways that we might want to create them in our code.

We could create a future explicitly:

let future = 
    async {
        try
            return (Success ...)
        with
        | e -> return (Failure e)
    }

However, in order to reduce the amount of boilerplate required and generally neaten our code we can create a helper function to wrap an Async<'T> to create a future:

//Async<'a> -> Future<'a>
let wrap computation = 
    async {

        let! choice = (Async.Catch computation)

        return (Outcome.ofChoice choice)
    }

This function accepts an Async<'T> and returns a Future<'T>. We use Async.Catch for exception handling which converts the Async<'T> to Aync<Choice<'T, exn>>. This Choice is then converted to an Outcome<'T> using a simple conversion function:

let ofChoice = function
    | Choice1Of2 value -> Success value
    | Choice2Of2 e -> Failure e

You may be asking why I did not just define Future<'T> using Choice<'T, exn>? My main reason is that I think Outcome<'T> has better semantics - Success and Failure have a clearer meaning than Choice1Of2 and Choice2Of2.

We can now create futures with minimal boilerplate and built-in exception handling:

let future = 
    Future.wrap (async {
        return ...
    })

Occasionally we may also want to create a future from a value:

let value x = wrap (async { return x })

This allows us to do this:

//Future<int>
let future = Future.value 10 

Working with futures

The Twitter paper uses several handy functions for working with futures. This section outlines how to implement those functions, as well as a few others which may come in handy.

flatMap

This function allows you to take the Success value of one future and create a new future by applying a mapping function to that value. This is analagous to the single argument case of the then function of AngularJS' $q service.

val flatMap = ('a -> Future<'b>) -> Future<'a> -> Future<'b>

For our definition of a future flatMap looks like this:

let flatMap f future = 
    async {

        //Evaluate the outcome of the first future
        let! outcome = future

        //Apply the mapping on success
        match outcome with
        | Success value -> return! (f value)
        | Failure e -> Failure e
    }

Futures can now be mapped like this:

let createRequest url = ...   //String -> Future<HttpWebRequest>
let parseResponse req = ...   //HttpWebRequest -> Data

//String -> Future<Data>
let getData = createRequest >> (Future.flatMap parseRequest)
map

This function allows us to perform simpler mappings on futures, and is similar to flatMap but instead of using a mapping function with signature 'a -> Future<'b> we use one that has signature 'a -> 'b.

//('a -> 'b) -> Future<'a> -> Future<'b>
let map f = 
    let f' value = wrap (async { return (f value) })
    in flatMap f'

This function creates an asychronous version of f and then uses wrap to convert its application to a future. We can now perform simple mappings on futures:

let getOrder orderId = ...     //Int32 -> Future<Order>
let getTotalCost order = ...   //Order -> Decimal

//Int32 -> Future<Decimal>
let getOrderCost = getOrder >> (Future.map getTotalCost)
rescue

As described in the Twitter paper it is sometimes possible to recover from a failure in a future and to continue as normal. The rescue functions works in a similar way to the flatMap function, allowing us to map a Failure outcome:

//(Exception -> Future<'a>) -> Future<'a> -> Future<'a>
let rescue f future = 
    async {

        let! outcome = future

        match outcome with
        | Success value -> return (Success value)
        | Failure e -> return! (f e)
    }

In use rescue looks like this:

let createRequest url = ...     //String -> Future<HttpWebRequest>
let getResponse req = ...       //HttpWebRequest -> Future<HttpWebResponse>
let handleWebException e = ...  //Exception -> Future<HttpWebResponse>

//String -> Future<HttpWebResponse>
let getData = 
    createRequest 
    >> (Future.flatMap getResponse) 
    >> (Future.rescue handleWebException)

Here, if getResponse results in a Failure outcome then handleWebException will be used to attempt to recover.

bind

The bind function allows us to combine two functions which return futures:

//('a -> Future<'b>) -> ('b -> Future<'c>) -> ('a -> Future<'c>)
let bind f g = f >> (flatMap g)

You will notice that I have used the >> (flatMap g) pattern in several examples already. This can now be replaced with bind, for example using the code from the section on flatMap:

//String -> Future<Data>
let getData = Future.bind createRequest parseResponse
chain

The chain function simply extends bind to accept any number of futures operating on the same type. This can be handy for creating piplines. In order to do this I use List.fold and bind to combine futures, with the value function being used as the starting point to accept the starting value.

//('a -> Future<'a>) list -> ('a -> Future<'a>)
let chain futures = 
    futures
    |> List.fold bind value

Note that writing this as simply let chain = List.fold bind value causes the compiler to prompt for type annotations which are more verbose than explicitly adding the futures parameter.

An example of using chain:

let setAuthenticationHeader req = ...   //HttpWebRequest -> Future<HttpWebRequest>
let setContentTypes req = ...           //HttpWebRequest -> Future<HttpWebRequest>
let writeBody body req = ...            //object -> HttpWebRequest -> Future<HttpWebRequest>

//object -> HttpWebRequest -> Future<HttpWebRequest>
let configureRequest body = 
    let writeBody' = writeBody body 
    in Future.chain [
        setAuthenticationHeader;
        setContentTypes;
        writeBody';
    ]

Operators

We can make our futures easier to use and more elegant by defining a few custom operators for bind, map and rescue:

    //('a -> Future<'b>) -> ('b -> Future<'c>) -> (a' -> Future<'c>)
    let (->>) = bind

    //('a -> Future<'b>) -> ('b -> 'c) -> ('a -> Future<'c>)
    let (-->) f g = f >> (map g)

    //('a -> Future<'b>) -> (Exception -> Future<'b>) -> ('a -> Future<'b>)
    let (--|) f g = f >> (rescue g)

These three operators let us build complex functions that return futures by composing several smaller functions that also return futures. This allows us to keep each function small and concise, as well as individually testable.

An example:

let createRequest url = ...     //String -> Future<HttpWebRequest>
let configureRequest req = ...  //HttpWebRequest -> Future<HttpWebRequest>
let getResponse req = ...       //HttpWebRequest -> Future<HttpWebResponse>
let handleException e = ...     //Exception -> Future<HttpWebResponse>
let verifyStatus res = ...      //HttpWebResponse -> Future<HttpWebResponse>
let parseResponse res = ...     //HttpWebResponse -> Future<Json>
let extractData json = ...      //Json -> Data

//String -> Future<Data>
let getData = 
    createRequest               //Create the request
    ->> configureRequest        //Set headers etc.
    ->> getResponse             //Get the response
    --| handleException         //Handle exceptions
    ->> verifyStatus            //Verify the status code
    ->> parseResponse           //Parse the response to JSON
    --> extractData             //Extract the pertinent data from the JSON

Here we create a future returning function by binding, rescuing and mapping using our three operators. A working example using the GitHub API can be found here on GitHub.

Conclusions

Futures are a useful programming tool, and in this post I have outlined how I have implemented futures in F#, taking inspiration from Scala's Future[T] type. A small set of utility functions and operators then allow you to create complex functions by composing smaller, more consise functions.

All of the code is available here on GitHub.

Comments