In the evolving landscape of F#, the introduction of the while!
(while-bang) keyword presents a refined approach to loops in computation expressions. Aiming to minimize boilerplate and maximize clarity, this new keyword is set to enhance the language’s expressive power.
Before while!
Consider a simple example: determining the latest ticket number (be it a PR or an issue) from the F# GitHub repository.
Here’s a naive approach to achieve this:
open System
open System.Net.Http
let client = new HttpClient()
let fsharpIssuesUrl = "https://github.com/dotnet/fsharp/issues"
let doesTicketExistAsync ticketNumber = task {
let uri = Uri $"{fsharpIssuesUrl}/{ticketNumber}"
let! response = client.GetAsync uri
return response.IsSuccessStatusCode
}
let goThroughFsharpTicketsAsync() = task {
let mutable ticketNumber = 1
let mutable keepGoing = true
while keepGoing do
match! doesTicketExistAsync ticketNumber with
| true ->
printfn $"Found a PR or issue #{ticketNumber}."
ticketNumber <- ticketNumber + 1
| false ->
keepGoing <- false
printfn $"#{ticketNumber} is not created yet."
}
goThroughFsharpTicketsAsync().Wait()
Due to the lack of a mechanism for an asynchronous condition in the while statement, we resort to using two mutable variables.
With while!
With the advent of the while!
keyword, specifying an asynchronous condition in loops becomes feasible.
open System
open System.Net.Http
let client = new HttpClient()
let fsharpIssuesUrl = "https://github.com/dotnet/fsharp/issues"
let doesTicketExistAsync ticketNumber = task {
let uri = Uri $"{fsharpIssuesUrl}/{ticketNumber}"
let! response = client.GetAsync uri
return response.IsSuccessStatusCode
}
let goThroughFsharpTicketsAsync() = task {
let mutable ticketNumber = 1
while! doesTicketExistAsync ticketNumber do
printfn $"Found a PR or issue #{ticketNumber}."
ticketNumber <- ticketNumber + 1
printfn $"#{ticketNumber} is not created yet."
}
goThroughFsharpTicketsAsync().Wait()
Notice the elimination of a mutable variable, a reduction in the total lines of code, and a decrease in overall cyclomatic complexity.
A few technical details
The while!
construct doesn’t require a dedicated builder method. Instead, it invokes .Bind
in the same manner as let!
does. This means that there’s no additional work needed when authoring new computation expressions. If you’re curious about the resulting syntax tree, you can inspect it within the tests of the feature’s PR, for example here.
Furthermore, these constructs are composable. For instance, they can be nested:
while! outerConditionAsync() do
while! innerConditionAsync() do
counter <- counter + 1
Try it out!
The while!
feature is set to be incorporated into F# 8. You can currently experiment with it using the flag --langversion:preview
. This flag can either be passed to the dotnet fsi invocation or inserted in your .fsproj file within .
Behind the new feature
The development of this functionality in F# is a testament to the language’s vibrant and engaged community. The new keyword was wholly added by one of our external contributors who implemented the correspondent language suggestion – thanks @kerams!
By the way, at the time of writing this post, ticket #15961 is the latest. Many tickets are good first issues and this is your cue to contribute! Language suggestions reside separately, and your insights there will be also very valued.
Very nice addition, cheers guys!