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!