{"id":47611,"date":"2023-09-20T10:05:00","date_gmt":"2023-09-20T17:05:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/dotnet\/?p=47611"},"modified":"2023-09-20T10:05:00","modified_gmt":"2023-09-20T17:05:00","slug":"simplifying-fsharp-computations-with-the-new-while-keyword","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/dotnet\/simplifying-fsharp-computations-with-the-new-while-keyword\/","title":{"rendered":"Simplifying F# computations with the new &#8216;while!&#8217; keyword"},"content":{"rendered":"<p>In the evolving landscape of F#, the introduction of the <code>while!<\/code> (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&#8217;s expressive power.<\/p>\n<h2>Before <code>while!<\/code><\/h2>\n<p>Consider a simple example: determining the latest ticket number (be it a PR or an issue) from the F# GitHub repository.<\/p>\n<p>Here&#8217;s a naive approach to achieve this:<\/p>\n<pre><code class=\"language-fsharp\">open System\nopen System.Net.Http\n\nlet client = new HttpClient()\nlet fsharpIssuesUrl = \"https:\/\/github.com\/dotnet\/fsharp\/issues\"\n\nlet doesTicketExistAsync ticketNumber = task {\n    let uri = Uri $\"{fsharpIssuesUrl}\/{ticketNumber}\"\n    let! response = client.GetAsync uri\n    return response.IsSuccessStatusCode\n}\n\nlet goThroughFsharpTicketsAsync() = task {\n    let mutable ticketNumber = 1\n    let mutable keepGoing = true\n\n    while keepGoing do\n        match! doesTicketExistAsync ticketNumber with\n        | true -&gt;\n            printfn $\"Found a PR or issue #{ticketNumber}.\"\n            ticketNumber &lt;- ticketNumber + 1\n        | false -&gt;\n            keepGoing &lt;- false\n            printfn $\"#{ticketNumber} is not created yet.\"\n}\n\ngoThroughFsharpTicketsAsync().Wait()<\/code><\/pre>\n<p>Due to the lack of a mechanism for an asynchronous condition in the while statement, we resort to using two mutable variables.<\/p>\n<h2>With <code>while!<\/code><\/h2>\n<p>With the advent of the <code>while!<\/code> keyword, specifying an asynchronous condition in loops becomes feasible.<\/p>\n<pre><code class=\"language-fsharp\">open System\nopen System.Net.Http\n\nlet client = new HttpClient()\nlet fsharpIssuesUrl = \"https:\/\/github.com\/dotnet\/fsharp\/issues\"\n\nlet doesTicketExistAsync ticketNumber = task {\n    let uri = Uri $\"{fsharpIssuesUrl}\/{ticketNumber}\"\n    let! response = client.GetAsync uri\n    return response.IsSuccessStatusCode\n}\n\nlet goThroughFsharpTicketsAsync() = task {\n    let mutable ticketNumber = 1\n\n    while! doesTicketExistAsync ticketNumber do\n        printfn $\"Found a PR or issue #{ticketNumber}.\"\n        ticketNumber &lt;- ticketNumber + 1\n\n    printfn $\"#{ticketNumber} is not created yet.\"\n}\n\ngoThroughFsharpTicketsAsync().Wait()<\/code><\/pre>\n<p>Notice the elimination of a mutable variable, a reduction in the total lines of code, and a decrease in overall cyclomatic complexity.<\/p>\n<h2>A few technical details<\/h2>\n<p>The <code>while!<\/code> construct doesn&#8217;t require a dedicated builder method. Instead, it invokes <code>.Bind<\/code> in the same manner as <code>let!<\/code> <a href=\"https:\/\/learn.microsoft.com\/dotnet\/fsharp\/language-reference\/computation-expressions#let\">does<\/a>. This means that there&#8217;s no additional work needed when authoring new computation expressions. If you&#8217;re curious about the resulting syntax tree, you can inspect it within the tests of the feature&#8217;s PR, for example <a href=\"https:\/\/github.com\/kerams\/fsharp\/blob\/a881a62a6d56ff90e70f91a1227a568f2f09d015\/tests\/service\/data\/SyntaxTree\/Expression\/WhileBang%2001.fs.bsl\">here<\/a>.<\/p>\n<p>Furthermore, these constructs are composable. For instance, they can be nested:<\/p>\n<pre><code class=\"language-fsharp\">while! outerConditionAsync() do\n    while! innerConditionAsync() do\n        counter &lt;- counter + 1<\/code><\/pre>\n<h2>Try it out!<\/h2>\n<p>The <code>while!<\/code> feature is set to be incorporated into F# 8. You can currently experiment with it using the flag <code>--langversion:preview<\/code>. This flag can either be passed to the dotnet fsi invocation or inserted in your .fsproj file within .<\/p>\n<h2>Behind the new feature<\/h2>\n<p>The development of this functionality in F# is a testament to the language&#8217;s vibrant and engaged community. The new keyword was wholly <a href=\"https:\/\/github.com\/dotnet\/fsharp\/pull\/14238\">added<\/a> by one of our external contributors who implemented the correspondent <a href=\"https:\/\/github.com\/fsharp\/fslang-suggestions\/issues\/1038\">language suggestion<\/a> &#8211; thanks @kerams!<\/p>\n<p>By the way, at the time of writing this post, ticket #15961 is the latest. Many tickets are <a href=\"https:\/\/github.com\/dotnet\/fsharp\/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22\">good first issues<\/a> and this is your cue to contribute! Language suggestions reside <a href=\"https:\/\/github.com\/fsharp\/fslang-suggestions\">separately<\/a>, and your insights there will be also very valued. <\/p>\n","protected":false},"excerpt":{"rendered":"<p>F# introduces `while!` keyword, streamlining loops in computation expressions.<\/p>\n","protected":false},"author":112074,"featured_media":47612,"comment_status":"open","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[685,636],"tags":[4,73],"class_list":["post-47611","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-dotnet","category-fsharp","tag-net","tag-f"],"acf":[],"blog_post_summary":"<p>F# introduces `while!` keyword, streamlining loops in computation expressions.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/47611","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/users\/112074"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/comments?post=47611"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/47611\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media\/47612"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media?parent=47611"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/categories?post=47611"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/tags?post=47611"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}