United Kingdom: +44 (0)208 088 8978

F# 8: the new while! keyword

Continuing our F# 8 series, Matt discusses the while! keyword introduced in F# 8 and when it's useful.

We're hiring Software Developers

Click here to find out more

This post is part of our F# 8 series, in which we'll be digging into a few of the best features in the latest version of the language.

The while! keyword was introduced in F# 8. Let's take a look at what it's used for.

For examples, I'll be using database tables and F# types inspired by (but not exactly the same as) those in the SQL series that Kash wrote a few years ago. Check out the ADO.NET post from that series if you want some more background on what's going on in the below code.

The problem: distracting boilerplate

Like many other languages, F# has while loops to repeatedly do something while a condition is true. Another great feature of F# is computation expression syntax, which provides a common syntax for workflows that make use of the same abstraction (monads). One common place to use such expressions is when programming asynchronous workflows.

Before F# 8, if you had an operation that returned a boolean value asynchronously, using the boolean value within a while loop within an asnyc or task computation expression required a bit of boilerplate. For example, the following code snippet requires binding firstRead, hasNext and nextRead, plus mutating hasNext at the end of each loop iteration.

let getAllPokemonTrainers () = task {

    use conn = new SqlConnection(connectionString)

    use cmd = new SqlCommand ("SELECT Id, Name, Wins, Losses FROM PokemonTrainer", conn )

    conn.Open()
    use reader = cmd.ExecuteReader()

    let ra = ResizeArray()

    let! firstRead = reader.ReadAsync()
    let mutable hasNext = firstRead
    while hasNext do
        ra.Add {
            Id = "Id" |> reader.GetOrdinal |> reader.GetGuid
            Name = "Name" |> reader.GetOrdinal |> reader.GetString
            Wins = "Wins" |> reader.GetOrdinal |> reader.GetInt32 |> uint32
            Losses = "Losses" |> reader.GetOrdinal |> reader.GetInt32 |> uint32
        }
        let! nextRead = reader.ReadAsync()
        hasNext <- nextRead

    return ra.ToArray()

}

This boilerplate adds complexity and draws attention away from the logic of the loop (adding items to an array).

The solution: while!

Compare and contrast the above snippet with the code below.

let getAllPokemonTrainersBang () = task {

    use conn = new SqlConnection(connectionString)

    use cmd = new SqlCommand ("SELECT Id, Name, Wins, Losses FROM PokemonTrainer", conn )

    conn.Open()
    use reader = cmd.ExecuteReader()

    let ra = ResizeArray()

    while! reader.ReadAsync() do
        ra.Add {
            Id = "Id" |> reader.GetOrdinal |> reader.GetGuid
            Name = "Name" |> reader.GetOrdinal |> reader.GetString
            Wins = "Wins" |> reader.GetOrdinal |> reader.GetInt32 |> uint32
            Losses = "Losses" |> reader.GetOrdinal |> reader.GetInt32 |> uint32
        }

    return ra.ToArray()

}

Using the while! keyword means that we can get the same behaviour but do away with the boilerplate of binding and mutating variables. This leaves the remaining code clearer and more focused.

Further reading

If you want to see more about while!, check out the the Microsoft DevBlog announcing it, the language suggestion requesting it and the pull request that implemented it.

As mentioned in the DevBlog, the feature was suggested by a community member and added by another. What a great demonstration of the value of an active open-source community like the one surrounding F#! Thanks mvkara and kerams!