{"id":48815,"date":"2023-11-14T08:00:00","date_gmt":"2023-11-14T16:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/dotnet\/?p=48815"},"modified":"2023-11-22T02:39:11","modified_gmt":"2023-11-22T10:39:11","slug":"announcing-fsharp-8","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/dotnet\/announcing-fsharp-8\/","title":{"rendered":"Announcing F# 8"},"content":{"rendered":"<p>F# 8 is released as part of .NET 8. It is included with new updates of Visual Studio 2022 and .NET 8 SDK.<\/p>\n<ul>\n<li><a href=\"https:\/\/dotnet.microsoft.com\/download\/dotnet\/8.0\">Download the latest version of .NET 8<\/a><\/li>\n<li><a href=\"https:\/\/visualstudio.microsoft.com\/vs\/preview\/\">Install Visual Studio 2022<\/a><\/li>\n<\/ul>\n<p>F# 8 brings in many features to make F# programs simpler, more uniform and more performant.\nRead more about language changes, new diagnostics, quality of life improvements, performance boosts for project compilation and upgrades given to the FSharp.Core standard library.<\/p>\n<p>This announcement lists the major changes brought in by F# 8 developed at <a href=\"https:\/\/github.com\/dotnet\/fsharp\">F#&#8217;s open source code repository<\/a>.<\/p>\n<p>Do you want to learn more about .NET 8 in general? Join us and many other speakers at <a href=\"https:\/\/dotnetconf.net\/\">.NET Conf 2023<\/a>, an online event scheduled for November 14th-16th.<\/p>\n<p>Are you new to F#? Start your journey at <a href=\"https:\/\/dotnet.microsoft.com\/languages\/fsharp\">the .NET guide to F# with learning materials, examples and YouTube videos<\/a>.<\/p>\n<p>Do you want to see how others are using F#? See recordings of <a href=\"http:\/\/fsharpconf.com\/\">this year&#8217;s fsharpConf<\/a>, the F# Community Virtual Conference. For more advanced topics as well as recordings of live contributions to F# compiler and the F# library ecosystem, see the <a href=\"https:\/\/amplifying-fsharp.github.io\/\">sessions of Amplifying F#<\/a>, a community initiative to grow F#.<\/p>\n<p>If you want to stay up-to-date with F#, follow @fsharponline on social media &#8211; <a href=\"https:\/\/www.linkedin.com\/company\/fsharponline\/\">LinkedIn<\/a>, <a href=\"https:\/\/twitter.com\/fsharponline\">X<\/a> and <a href=\"https:\/\/hachyderm.io\/@fsharponline\">Hachyderm<\/a>.<\/p>\n<h2>F# language changes<\/h2>\n<p>This section describes updates to the language itself, changes you will notice when writing or reading F# code the most.\nMost of the code samples in this blog post are duplicated in the <a href=\"https:\/\/github.com\/T-Gro\/FSharp8_news\">FSharp 8 News repository<\/a> as a F# project. If you installed the latest .NET 8 tooling, you can check it out and experiment with the code already.<\/p>\n<p>Do you have any interesting use cases for the new features you want to make public for others?\nLet me know in <a href=\"https:\/\/github.com\/T-Gro\/FSharp8_news\">that repository<\/a> via a new issue or open a direct pull request straight away!<\/p>\n<h3>_.Property shorthand for (fun x -&gt; x.Property)<\/h3>\n<p>The first feature I want to introduce is a shorthand for defining simple lambda functions &#8211; useful for situations, when a lambda only does an atomic expression on the lambda argument. An atomic expression is an expression which has no whitespace unless enclosed in method call parentheses.<\/p>\n<p>Let&#8217;s have a look at a practical example before and after this feature.<\/p>\n<p><strong>Before:<\/strong><\/p>\n<pre><code class=\"language-fsharp\">type Person = {Name : string; Age : int}\r\nlet people = [ {Name = \"Joe\"; Age = 20} ; {Name = \"Will\"; Age = 30} ; {Name = \"Joe\"; Age = 51}]\r\n\r\nlet beforeThisFeature = \r\n    people \r\n    |&gt; List.distinctBy (fun x -&gt; x.Name)\r\n    |&gt; List.groupBy (fun x -&gt; x.Age)\r\n    |&gt; List.map (fun (x,y) -&gt; y)\r\n    |&gt; List.map (fun x -&gt; x.Head.Name)\r\n    |&gt; List.sortBy (fun x -&gt; x.ToString())\r\n<\/code><\/pre>\n<p><strong>After:<\/strong><\/p>\n<pre><code class=\"language-fsharp\">type Person = {Name : string; Age : int}\r\nlet people = [ {Name = \"Joe\"; Age = 20} ; {Name = \"Will\"; Age = 30} ; {Name = \"Joe\"; Age = 51}]\r\n\r\nlet possibleNow = \r\n    people \r\n    |&gt; List.distinctBy _.Name\r\n    |&gt; List.groupBy _.Age\r\n    |&gt; List.map snd\r\n    |&gt; List.map _.Head.Name\r\n    |&gt; List.sortBy _.ToString()\r\n<\/code><\/pre>\n<p>As you can see, the snippet <code>(fun x -&gt; x.)<\/code> got replaced with just <code>_.<\/code>, and the need for parantheses was eliminated.\nThis can come especially handy in a sequence of <code>|&gt;<\/code> pipelined calls, as typically used for F#&#8217;s combinators in List, Option and many other modules.\nThe feature works for single property access, nested property access, method calls and even indexers. The example demonstrates this feature on a list of records, but it works on all values that you can use in a regular lambda function, too. So for example also covering objects, primitives, anonymous records or discriminated unions:<\/p>\n<pre><code class=\"language-fsharp\">let getIdx5 : {| Foo : int array |} -&gt; int = _.Foo[5]\r\n<\/code><\/pre>\n<p><strong>Also:<\/strong><\/p>\n<p>What&#8217;s more, this feature can also be used outside of a function call to define a standalone lambda to future usage.\nIn the second example <code>getNameLength<\/code>, you can also see that the feature works without the need to annotate the definition.<\/p>\n<pre><code class=\"language-fsharp\">let ageAccessor : Person -&gt; int = _.Age\r\nlet getNameLength = _.Name.Length\r\n<\/code><\/pre>\n<p>The same syntax can be used to define accessors via <a href=\"https:\/\/learn.microsoft.com\/dotnet\/fsharp\/language-reference\/generics\/statically-resolved-type-parameters\">SRTP syntax<\/a>, allowing the same binding to be used by all items having the same member, without the need to share a common interface.<\/p>\n<pre><code class=\"language-fsharp\">let inline myPropGetter (x: 'a when 'a:(member WhatANiceProperty:string)) = \r\n      x |&gt; _.WhatANiceProperty\r\n<\/code><\/pre>\n<p>There is a situation where this syntax is not suitable: when the surrounding scope already makes use of the <code>_<\/code> underscore symbol, typically to discard a parameter.<\/p>\n<pre><code class=\"language-fsharp\">let a : string -&gt; string = (fun _ -&gt; 5 |&gt; _.ToString())\r\n<\/code><\/pre>\n<p>Such code will end up producing a warning FS3570, saying <code>\"The meaning of _ is ambiguous here. It cannot be used for a discarded variable and a function shorthand in the same scope.\"<\/code><\/p>\n<h3>Nested record field copy and update<\/h3>\n<p>Next new feature is a <a href=\"https:\/\/learn.microsoft.com\/dotnet\/fsharp\/language-reference\/copy-and-update-record-expressions\">copy-and-update<\/a> enhancement for nested records.\nLet&#8217;s again demonstrate the feature with before and after examples.<\/p>\n<p><strong>Before:<\/strong><\/p>\n<pre><code class=\"language-fsharp\">type SteeringWheel = { Type: string }\r\ntype CarInterior = { Steering: SteeringWheel; Seats: int }\r\ntype Car = { Interior: CarInterior; ExteriorColor: string option }\r\n\r\nlet beforeThisFeature x = \r\n    { x with Interior = { x.Interior with \r\n                            Steering = {x.Interior.Steering with Type = \"yoke\"}\r\n                            Seats = 5\r\n                        }\r\n    }\r\n<\/code><\/pre>\n<p><strong>After:<\/strong><\/p>\n<pre><code class=\"language-fsharp\">let withTheFeature x = { x with Interior.Steering.Type = \"yoke\"; Interior.Seats = 5 }\r\n<\/code><\/pre>\n<p>The two blocks make the identical change. Instead of having to write multiple nested <code>with<\/code> keywords, the new language feature allows you to use the dot-notation to reach to lower levels of nested records and update those.\nAs seen in the example, the syntax still allows copy-and-updating multiple fields using the same expression. Each copy-and-update within the same expression can be on a different level of nesting (see <code>Interior.Steering.Type<\/code> and <code>Interior.Seats<\/code> being used in the same snippet above).<\/p>\n<p><strong>Also works for anonymous records:<\/strong>\nThe same syntax extension can be used on anonymous records, or when updating regular records into anonymous ones.\nUsing the same type definitions from the example above, we can update the field <code>Interior.Seats<\/code> using the new feature as well as as add a brand new field <code>Price<\/code> within the same expression.<\/p>\n<pre><code class=\"language-fsharp\">let alsoWorksForAnonymous (x:Car) = {| x with Interior.Seats = 7; Price = 99_999 |}\r\n<\/code><\/pre>\n<p><strong>Beware of name clashes between types and record fields:<\/strong><\/p>\n<p>When trying out this feature, you <a href=\"https:\/\/github.com\/dotnet\/fsharp\/issues\/16180\">might get into a conflict<\/a> when naming a field the same as an existing type.\nBefore F# 8, it was possible to qualify record updates using the <code>Type.Field<\/code> notation. For backwards compatibility, this behavior still works and has a higher priority over a new language feature.<\/p>\n<pre><code class=\"language-fsharp\">type Author = {\r\n    Name: string\r\n    YearBorn: int\r\n}\r\ntype Book = {\r\n    Title: string\r\n    Year: int\r\n    Author: Author\r\n}\r\n\r\nlet oneBook = { Title = \"Book1\"; Year = 2000; Author = { Name = \"Author1\"; YearBorn = 1950 } }\r\nlet codeWhichWorks = {oneBook with Book.Author.Name = \"Author1Updated\"}\r\nlet codeWhichLeadsToAnError = {oneBook with Author.Name = \"Author1Updated\"}\r\n<\/code><\/pre>\n<p>The last example, since it has to prefer to pre-existing <code>Type.Field<\/code> notation for the existing <code>Author<\/code> type, leads to an error:<\/p>\n<p><code>This expression was expected to have type 'Book' but here has type 'Author'<\/code><\/p>\n<p><strong>Workaround:<\/strong>\nWhen this happens, it is sufficient to qualify the update with the right type, as in the <code>codeWhichWorks<\/code> sample: <code>Book.Author.Name<\/code> works fine here.<\/p>\n<h3>while!<\/h3>\n<p>The <code>while!<\/code> (while bang) feature was announced when as part of an earlier preview. You can read in <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/simplifying-fsharp-computations-with-the-new-while-keyword\/\">blog post introducing while!<\/a>.<\/p>\n<p>What does <code>while!<\/code> do?\nIt simplifies the usage of computation expressions when looping over a boolean condition that has to be evaluated by the computation expression first (e.g., inside an <code>async{}<\/code> block).<\/p>\n<p><strong>Before this feature:<\/strong><\/p>\n<pre><code class=\"language-fsharp\">let mutable count = 0\r\nlet asyncCondition = async {\r\n    return count &lt; 10\r\n}\r\n\r\nlet doStuffBeforeThisFeature = \r\n    async {\r\n       let! firstRead = asyncCondition\r\n       let mutable read = firstRead\r\n       while read do\r\n         count &lt;- count + 2\r\n         let! nextRead = asyncCondition\r\n         read &lt;- nextRead\r\n       return count\r\n    }\r\n<\/code><\/pre>\n<p><strong>With <code>while!<\/code>:<\/strong><\/p>\n<pre><code class=\"language-fsharp\">let doStuffWithWhileBang =\r\n    async {\r\n        while! asyncCondition do\r\n            count &lt;- count + 2\r\n        return count\r\n    }\r\n<\/code><\/pre>\n<p>The two code blocks are equivalent in their behavior.<\/p>\n<p>The addition of <code>while!<\/code> means a reduction of boilerplate code needed to maintain the <code>mutable read<\/code> boolean variable which was being looped over. The regular <code>while<\/code> (no bang) can only loop over a bool value, and not over an <code>async&lt;bool&gt;<\/code> (or similar wrapper in a different computation expression). <code>while!<\/code> brings this possibility to the language.<\/p>\n<p>The <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/simplifying-fsharp-computations-with-the-new-while-keyword\/\">separate blog post<\/a> uses the <code>&lt;LangVersion&gt;preview&lt;\/LangVersion&gt;<\/code> project setting to enable it. That is no longer needed with .NET 8 being released. As a <a href=\"https:\/\/learn.microsoft.com\/dotnet\/fsharp\/whats-new\/fsharp-47#language-version\">reminder<\/a>, newly developed language and compiler features can be tested using the <code>preview<\/code> value before the final version (in this case, 8) is released.<\/p>\n<h3>Extended string interpolation syntax<\/h3>\n<p>F# 8 took inspiration from <a href=\"https:\/\/learn.microsoft.com\/dotnet\/csharp\/language-reference\/tokens\/interpolated#interpolated-raw-string-literals\">interpolated raw string literals in C#<\/a> and improved the support for existing <a href=\"https:\/\/learn.microsoft.com\/dotnet\/fsharp\/language-reference\/interpolated-strings\">interpolated strings in F#<\/a>.<\/p>\n<p>In interpolated strings, literal text output can be combined with values and expressions by wrapping them into a pair of braces <code>{}<\/code>.\nBraces therefore are special symbols, and need to be escaped by doubling them (via <code>{{<\/code> and <code>}}<\/code> ) if they are meant to be a literal part of the output.<\/p>\n<p>This gets in the way if the text naturally contains a lot of braces, e.g. in the context of embedding other languages in F# strings &#8211; JSON, CSS or HTML-based templating languages such as <a href=\"https:\/\/mustache.github.io\/\">mustache<\/a>.<\/p>\n<p><strong>Embedding CSS in F# strings before:<\/strong>\nNotice how each brace for the literal output had to be doubled.<\/p>\n<pre><code class=\"language-fsharp\">let classAttr = \"item-panel\"\r\nlet cssOld = $\"\"\".{classAttr}:hover {{background-color: #eee;}}\"\"\"\r\n<\/code><\/pre>\n<p>The extended interpolation syntax introduces the ability to redefine that behaviour by entering multiple <code>$<\/code> dollar signs at the beginning of the interpolated string literal. The number of starting dollars then dictates the number of braces needed to enter interpolation mode. Any smaller number of braces just becomes part of the output, without any need to escape them.<\/p>\n<p><strong>With new feature:<\/strong><\/p>\n<pre><code class=\"language-fsharp\">let cssNew = $$\"\"\".{{classAttr}}:hover {background-color: #eee;}\"\"\"\r\n<\/code><\/pre>\n<p><strong>HTML templating:<\/strong>\nIn HTML-based templating languages, doubled braces are commonly used to render variables.\nIn the example below, you can see how F# strings compare with and without extended interpolation syntax.<\/p>\n<pre><code class=\"language-fsharp\">let templateOld = $\"\"\"\r\n&lt;div class=\"{classAttr}\"&gt;\r\n  &lt;p&gt;{{{{title}}}}&lt;\/p&gt;\r\n&lt;\/div&gt;\r\n\"\"\"\r\nlet templateNew = $$$\"\"\"\r\n&lt;div class=\"{{{classAttr}}}\"&gt;\r\n  &lt;p&gt;{{title}}&lt;\/p&gt;\r\n&lt;\/div&gt;\r\n\"\"\"\r\n<\/code><\/pre>\n<p>You can read more about the feature in the <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/new-syntax-for-string-interpolation-in-fsharp\/\">blog post about new syntax for string interpolation in F# 8<\/a>.<\/p>\n<h3>Use and compose string literals for printf and related functions<\/h3>\n<p>String literals have received one more update in this release, for usages of built-in printing functions (printfn, sprintfn <a href=\"https:\/\/fsharpforfunandprofit.com\/posts\/printf\/#the-printf-family-of-functions\">and others<\/a>).<\/p>\n<p><strong>Before F# 8:<\/strong><\/p>\n<pre><code class=\"language-fsharp\">let renderedCoordinatesOld = sprintf \"(%f,%f)\" 0.25 0.75\r\nlet renderedTextOld = sprintf \"Person at coordinates(%f,%f)\" 0.25 0.75\r\n<\/code><\/pre>\n<p>Before F# 8, the format specified has to be a string literal typed directly at the place of usage.\nWith F# 8, string literals defined elsewhere are supported.\nWhat&#8217;s more, you can define a string literal using a concatenation of existing string literals.\nThat way, commonly repeating format specifiers can be reused as patterns, instead of repeating same string snippets throughout the codebase.<\/p>\n<p>Why does it have to be a <code>[&lt;Literal&gt;]<\/code> string that is known at compile time? Since the compiler is doing typechecking on the format string in conjuction with other arguments, full content of it must be known at compile time and it cannot be a runtime value.<\/p>\n<p><strong>String format reuse with F# 8:<\/strong><\/p>\n<pre><code class=\"language-fsharp\">[&lt;Literal&gt;] \r\nlet formatBody = \"(%f,%f)\"\r\n[&lt;Literal&gt;] \r\nlet formatPrefix = \"Person at coordinates\"\r\n[&lt;Literal&gt;] \r\nlet fullFormat = formatPrefix + formatBody\r\n\r\nlet renderedCoordinates = sprintf formatBody 0.25 0.75\r\nlet renderedText = sprintf fullFormat 0.25 0.75\r\n<\/code><\/pre>\n<h3>Arithmetic operators in literals<\/h3>\n<p>Numeric literals have also received an update.\nIn the past, they had to be fully specified using constant values.<\/p>\n<p><strong>Before:<\/strong><\/p>\n<pre><code class=\"language-fsharp\">module ArithmeticLiteralsBefore =\r\n    let [&lt;Literal&gt;] bytesInKB = 1024f\r\n    let [&lt;Literal&gt;] bytesInMB = 1048576f\r\n    let [&lt;Literal&gt;] bytesInGB = 1073741824\r\n    let [&lt;Literal&gt;] customBitMask =  0b01010101uy\r\n    let [&lt;Literal&gt;] inverseBitMask = 0b10101010uy\r\n<\/code><\/pre>\n<p>With F# 8, numeric literals can also be expressed using existing operators and other literals.\nThe compiler evaluates the expression at compile time, and stores the resulting value in the produced assembly. You will notice this when you hover over a defined literal (or its usage) in Visual Studio &#8211; it will show you the calculated value.<\/p>\n<ul>\n<li>Supported for numeric types: <code>+,-,*, \/, %, &amp;&amp;&amp;, |||, &lt;&lt;&lt;, &gt;&gt;&gt;, ^^^, ~~~, **<\/code>\n<ul>\n<li>The operators have the same semantics as they have in non-literal expressions.<\/li>\n<\/ul>\n<\/li>\n<li>Supported for bools: <code>not, &amp;&amp;, ||<\/code><\/li>\n<\/ul>\n<p><strong>Example using F# 8:<\/strong><\/p>\n<pre><code class=\"language-fsharp\">\r\nlet [&lt;Literal&gt;] bytesInKB = 2f ** 10f\r\nlet [&lt;Literal&gt;] bytesInMB = bytesInKB * bytesInKB\r\nlet [&lt;Literal&gt;] bytesInGB = 1 &lt;&lt;&lt; 30\r\nlet [&lt;Literal&gt;] customBitMask = 0b01010101uy\r\nlet [&lt;Literal&gt;] inverseBitMask = ~~~ customBitMask\r\n<\/code><\/pre>\n<p><strong>Also works for enum values and literals:<\/strong><\/p>\n<p>The feature can be used at places which require literal values, such as enum values or attribute parameters.<\/p>\n<pre><code class=\"language-fsharp\">type MyEnum = \r\n    | A = (1 &lt;&lt;&lt; 5)\r\n    | B = (17 * 45 % 13)\r\n    | C = bytesInGB\r\n\r\n[&lt;System.Runtime.CompilerServices.MethodImplAttribute(enum(1+2+3))&gt;]\r\nlet doStuff = ()\r\n<\/code><\/pre>\n<p>In the case of enum values, arithmetic expressions need to be wrapped in parentheses like in the example above.<\/p>\n<h3>Type constraint intersection syntax<\/h3>\n<p>F# 8 brings in a new feature to simplify definition of multiple intersected generic constraints using <a href=\"https:\/\/learn.microsoft.com\/dotnet\/fsharp\/language-reference\/flexible-types\">flexible types<\/a>.<\/p>\n<p><strong>Code prior to F# 8:<\/strong><\/p>\n<pre><code class=\"language-fsharp\">let beforeThis(arg1 : 't \r\n    when 't:&gt;IDisposable \r\n    and 't:&gt;IEx \r\n    and 't:&gt;seq&lt;int&gt;) =\r\n    arg1.h(arg1)\r\n    arg1.Dispose()\r\n    for x in arg1 do\r\n        printfn \"%i\" x\r\n<\/code><\/pre>\n<p>The definiton does require repeating the generic type argument <code>'t<\/code> for every clause and they have to be connected using the <code>and<\/code> keyword.\nWith F# 8, intersected constrains can be specified using the <code>&amp;<\/code> characters and achieve the same result:<\/p>\n<pre><code class=\"language-fsharp\">let withNewFeature (arg1: 't &amp; #IEx &amp; \r\n    #IDisposable &amp; #seq&lt;int&gt;) =\r\n    arg1.h(arg1)\r\n    arg1.Dispose()\r\n    for x in arg1 do\r\n        printfn \"%i\" x\r\n<\/code><\/pre>\n<p>The same syntax also works on specifying signatures, e.g. in signature files or for defining abstract functions:<\/p>\n<pre><code class=\"language-fsharp\">type IEx =\r\n    abstract h: #IDisposable &amp; #seq&lt;int&gt; -&gt; unit\r\n<\/code><\/pre>\n<h3>Extended fixed bindings<\/h3>\n<p><a href=\"https:\/\/learn.microsoft.com\/dotnet\/fsharp\/language-reference\/fixed\">F# has the fixed keyword<\/a>, which can be used to pin memory and get its address as <code>nativeptr&lt;int&gt;<\/code>. It is used in low-level programming scenarios.<\/p>\n<p><a href=\"https:\/\/github.com\/fsharp\/fslang-design\/blob\/main\/FSharp-8.0\/FS-1081-extended-fixed-bindings.md\">F# 8 extends this feature<\/a> with additionally allowing it on:<\/p>\n<ul>\n<li><code>byref&lt;'t&gt;<\/code><\/li>\n<li><code>inref&lt;'t&gt;<\/code><\/li>\n<li><code>outref&lt;'t&gt;<\/code><\/li>\n<li>any type &#8216;a when &#8216;a has an instance\/extension method <code>GetPinnableReference : unit -&gt; byref&lt;'t&gt;<\/code><\/li>\n<li>any type &#8216;a when &#8216;a has an instance\/extension method <code>GetPinnableReference : unit -&gt; inref&lt;'t&gt;<\/code><\/li>\n<\/ul>\n<p>The last two additions are especially relevant for the growing ecosystem and the types like <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.readonlyspan-1.getpinnablereference?view=net-7.0\">ReadOnlySpan<\/a> or <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.span-1.getpinnablereference?view=net-7.0\">Span<\/a>.<\/p>\n<p><strong>Possible now:<\/strong><\/p>\n<pre><code class=\"language-fsharp\">open System\r\nopen FSharp.NativeInterop\r\n\r\n#nowarn \"9\"\r\n\/\/ \"Warning no. 9 is a warning about using unsafe code with nativeint. We are disabling it here when we know we know what we are doing\"\r\nlet pinIt (span: Span&lt;char&gt;, byRef: byref&lt;int&gt;, inRef: inref&lt;int&gt;) =\r\n    \/\/ Calls span.GetPinnableReference()\r\n    \/\/ The following lines wouldn't compile before\r\n    use ptrSpan = fixed span\r\n    use ptrByRef = fixed &amp;byRef\r\n    use ptrInref = fixed &amp;inRef\r\n\r\n    NativePtr.copyBlock ptrByRef ptrInref 1\r\n<\/code><\/pre>\n<h3>Easier <code>[&lt;Extension&gt;]<\/code> method definition<\/h3>\n<p>The <code>[&lt;Extension&gt;]<\/code> attribute exists in order to define C#-style extension methods, which can be consumed both by F# and C#.\nHowever, in order to satisfy the C# compiler, the attribute had to be applied both on the member as well as on the type:<\/p>\n<p><strong>Before:<\/strong><\/p>\n<pre><code class=\"language-fsharp\">open System.Runtime.CompilerServices\r\n[&lt;Extension&gt;]\r\ntype Foo =\r\n    [&lt;Extension&gt;]\r\n    static member PlusOne (a:int) : int = a + 1\r\nlet f (b:int) = b.PlusOne()\r\n<\/code><\/pre>\n<p>With F# 8, the compiler will only require the attribute on the extension method, and will automatically add the type-level attribute for you.<\/p>\n<p><strong>After:<\/strong><\/p>\n<pre><code class=\"language-fsharp\">open System.Runtime.CompilerServices\r\ntype Foo =\r\n    [&lt;Extension&gt;]\r\n    static member PlusOne (a:int) : int = a + 1\r\nlet f (b:int) = b.PlusOne()\r\n<\/code><\/pre>\n<h2>Making F# more uniform<\/h2>\n<p>The following changes are making F# more consistent by allowing existing constructs at previously forbidden contexts.\nThis aims to reduce beginner confusion, reduce the need for workarounds and lead to a more succinct code.<\/p>\n<h3>Static members in interfaces<\/h3>\n<p>This change allows to declare and implement static members in interfaces.\nNot to confuse with <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/announcing-fsharp-7\/#static-abstract-members-support-in-interfaces\">static abstract members in interfaces from F#7<\/a>, the upcoming change is about concrete members in interfaces, with their implementation.<\/p>\n<p>Today, the same would be typically accomplished by putting a module with implemented function(s) below the type.<\/p>\n<p><strong>Before:<\/strong><\/p>\n<pre><code class=\"language-fsharp\">[&lt;Interface&gt;]\r\ntype IDemoableOld =\r\n    abstract member Show: string -&gt; unit\r\n\r\nmodule IDemoableOld =\r\n    let autoFormat(a) = sprintf \"%A\" a\r\n<\/code><\/pre>\n<p>With F# 8, interfaces can have concrete members on them and a standalone module is not needed.<\/p>\n<p><strong>After:<\/strong><\/p>\n<pre><code class=\"language-fsharp\">[&lt;Interface&gt;]\r\ntype IDemoable =\r\n    abstract member Show: string -&gt; unit\r\n    static member AutoFormat(a) = sprintf \"%A\" a\r\n<\/code><\/pre>\n<h3>Static let in discriminated unions, records, structs, and types without primary constructors<\/h3>\n<p>Second example of uniformity is enablement of static <a href=\"https:\/\/learn.microsoft.com\/dotnet\/fsharp\/language-reference\/members\/let-bindings-in-classes\">bindings<\/a> in more F# types. We are talking about the following constructs:<\/p>\n<ul>\n<li><code>static let<\/code><\/li>\n<li><code>static let mutable<\/code><\/li>\n<li><code>static do<\/code><\/li>\n<li><code>static member val<\/code><\/li>\n<\/ul>\n<p>Before F# 8, those were only possible in regular class definitions. With F# 8, they can be added to:<\/p>\n<ul>\n<li>Discriminated unions<\/li>\n<li>Records<\/li>\n<li>Structs\n<ul>\n<li>incl. <code>[&lt;Struct&gt;]<\/code> unions and records<\/li>\n<\/ul>\n<\/li>\n<li>Types without primary constructors<\/li>\n<\/ul>\n<p>This addition can again help with encapsulating data and logic within the type definition, as opposed to declaring a standalone <code>module<\/code> and putting the bindings in that module.<\/p>\n<p><strong>Possible now:<\/strong>\nAs an example, a simple lookup for converting from string to cases of a disriminated union can now be declared directly inside the type definiton.<\/p>\n<pre><code class=\"language-fsharp\">open FSharp.Reflection\r\n\r\ntype AbcDU = A | B | C\r\n    with   \r\n        static let namesAndValues = \r\n            FSharpType.GetUnionCases(typeof) \r\n            |&gt; Array.map (fun c -&gt; c.Name, FSharpValue.MakeUnion (c,[||]) :?&gt; AbcDU)\r\n        static let stringMap = namesAndValues |&gt; dict\r\n        static let mutable cnt = 0\r\n\r\n        static do printfn \"Init done! We have %i cases\" stringMap.Count\r\n        static member TryParse text = \r\n            let cnt = Interlocked.Increment(&amp;cnt)\r\n            stringMap.TryGetValue text, sprintf \"Parsed %i\" cnt\r\n<\/code><\/pre>\n<p>The example also shows how a static mutable can be added, and how side effects can be triggered via <a href=\"https:\/\/learn.microsoft.com\/dotnet\/fsharp\/language-reference\/members\/do-bindings-in-classes\">static do<\/a>. This feature respects the declaration order within the file, as well as the declaration order of types and bindings within a single <code>module<\/code>.<\/p>\n<p><strong>Also possible:<\/strong><\/p>\n<p>The syntax also allows you to define <a href=\"https:\/\/learn.microsoft.com\/dotnet\/fsharp\/language-reference\/members\/explicit-fields-the-val-keyword\">explicit static fields with automatic properties using the &#8216;member val&#8217; keywords<\/a>.<\/p>\n<pre><code class=\"language-fsharp\">type AnotherDu = D | E\r\n    with\r\n        static member val X = 42 with get,set\r\n<\/code><\/pre>\n<p>Active patterns can also be implemented in <code>static let<\/code> bindings. The active pattern is private to the type, but it can be used by following members within the same type definition.<\/p>\n<pre><code class=\"language-fsharp\">type AB = \r\n    | A\r\n    | B of int\r\n\r\n    static let (|B0PatPrivate|_|) value = if value = 0 then Some (B 999) else None\r\n    static member ParseUsingActivePattern x = match x with | B0PatPrivate x -&gt; x | _ -&gt; A\r\n\r\nlet testThis = AB.ParseUsingActivePattern 0\r\n<\/code><\/pre>\n<p><strong>Good to know:<\/strong>\nThis feature does not work for &#8216;types&#8217; which are not real types at runtime. In particular type aliases and units of measure, which are being erased during compilation, and plain .NET enums.<\/p>\n<p>As with other statics in generic types in .NET, all static values are created per generic instantiation and can make use of the generic parameters provided, e.g. via reflection.<\/p>\n<p>The following example shows a <code>static let<\/code> in a generic type, in particular a type without primary constructor.<\/p>\n<pre><code class=\"language-fsharp\">type EmptyT =\r\n    static let cachedName = \r\n        let name = typeof.Name\r\n        printfn \"Accessing name for %s\" name\r\n        name\r\n    static member Name = cachedName\r\n<\/code><\/pre>\n<p>This might be useful to retrieve and cache information retrieved by reflection or by runtime-only features such as the <code>sizeof<\/code> instruction, per generic type instantiation.<\/p>\n<pre><code class=\"language-fsharp\">[&lt;Struct&gt;]\r\ntype MyUnion = \r\n    | A of aval:'A\r\n    | B of bval:'B\r\n    | C\r\n\r\n    static let sizeOfTCached = \r\n        printfn \"Creating cached val for %s * %s\" (typeof.Name) (typeof.Name)\r\n        sizeof&lt;MyUnion&gt;\r\n<\/code><\/pre>\n<h3><code>try-with<\/code> within <code>seq{}<\/code>,<code>[]<\/code> and <code>[||]<\/code> collection expressions<\/h3>\n<p>Third addition in the &#8216;uniformity&#8217; category is new support of the the try-with code construct within collection builders &#8211; <code>seq{}<\/code> for <code>IEnumerable<\/code> definitions, <code>[]<\/code> list builders, and <code>[||]<\/code> array builders.<\/p>\n<p>With this change, exception handling in those expressions is possible.\nThe following combinations are possible:<\/p>\n<ul>\n<li>&#8216;try&#8217; part can yield values, &#8216;with&#8217; can just produce a side effect (e.g. logging)<\/li>\n<li>both &#8216;try&#8217; and &#8216;with&#8217; can yield values<\/li>\n<li>&#8216;try&#8217; can be empty from the perspective of yielding values, and only &#8216;with&#8217; produces data\n<ul>\n<li>This scenario is less likely to be meaningful in real code, but it is supported<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>In the examples below, exception throwing is simulated by dividing by 0, which produces a <code>DivideByZeroException<\/code> on .NET.<\/p>\n<p><strong>Possible now:<\/strong><\/p>\n<pre><code class=\"language-fsharp\">let sum =\r\n    [ for x in [0;1] do       \r\n            try          \r\n                yield 1              \r\n                yield (10\/x)    \r\n                yield 100  \r\n            with _ -&gt;\r\n                yield 1000 ]\r\n    |&gt; List.sum\r\n<\/code><\/pre>\n<p>When executed, the code yields a list <code>[1;1000;1;10;100]<\/code> which sums up to 1112.<\/p>\n<p><strong>Also: recursive calls and <code>yield!<\/code><\/strong><\/p>\n<p>Recursive calls from within the exception handler are also supported. In the case of <code>seq{}<\/code>, the behavior still maintains the lazy semantics and only executes code one value at a time. The example below shows how the exception handler can &#8220;retry&#8221; by recursively <code>yield!<\/code>-ing the same calculation again.<\/p>\n<p>Do note that exception handling (the <code>'with'<\/code> handler) has a non-trivial cost in .NET which will be noticable in case of repetitive exceptions being thrown and handled.<\/p>\n<pre><code class=\"language-fsharp\">let rec f () = seq {\r\n    try \r\n        yield 123    \r\n        yield (456\/0)\r\n    with exn -&gt;\r\n        eprintfn \"%s\" exn.Message\r\n        yield 789\r\n        yield! f()\r\n}\r\n\r\nlet first5 = \r\n    f() \r\n    |&gt; Seq.take 5 \r\n    |&gt; Seq.toArray\r\n<\/code><\/pre>\n<p>When executed, the code produces <code>[|123; 789; 123; 789; 123|]<\/code>.<\/p>\n<h2>New diagnostics<\/h2>\n<p>A big part of new F# 8 development is dedicated to new and updated diagnostics.\nThose are the errors, warnings and information messages which compiler reports when it encounters issues.\nThis section lists a few selected categories of diagnostics enhancements.\nBetween F# 7 and F# 8, 34 new diagnostic errors and messages have been added into the <a href=\"https:\/\/github.com\/dotnet\/fsharp\/blame\/main\/src\/Compiler\/FSComp.txt\">F# definition of diagnostical messages<\/a>.<\/p>\n<h3>TailCall attribute<\/h3>\n<p>In F#, <a href=\"https:\/\/learn.microsoft.com\/dotnet\/fsharp\/language-reference\/functions\/recursive-functions-the-rec-keyword\">recursive functions<\/a> are possible using the &#8216;rec&#8217; keyword. An important aspect of recursive functions is the ability to do <a href=\"https:\/\/learn.microsoft.com\/dotnet\/fsharp\/language-reference\/functions\/recursive-functions-the-rec-keyword#tail-recursion\">tail recursion<\/a> in order to prevent deep stacks and eventually StackOverflowException errors.<\/p>\n<p>You can also read <a href=\"https:\/\/cs.stackexchange.com\/questions\/6230\/what-is-tail-recursion\">this Q&amp;A<\/a> to learn what tail recursion means independent of the F# programming language.<\/p>\n<p>Starting with F# 8.0, you can use the <code>TailCall<\/code> attribute to explicitly state your intention of defining a tail-recursive function to the compiler. The compiler will then warn you if your function makes non-tail recursive calls. You can use the attribute on methods and module-level functions.<\/p>\n<p>The attribute is purely informational to the compiler and doesn&#8217;t affect generated code, it ensures the compiler checks for tail call consistency and raises a warning if it is not met.<\/p>\n<p><strong>Possible now:<\/strong>\nThe first function, <code>factorialClassic<\/code> is the text book example for starting with functional programming and implementing the factorial function.\nIn this snippet, it is not tail recursive: When building the project, a <code>\"warning FS3569: The member or function 'factorialClassic' has the 'TailCallAttribute' attribute, but is not being used in a tail recursive way.\"<\/code> is produced.<\/p>\n<p>The second function, <code>factorialWithAcc<\/code>, uses the accummulator technique to implement the calculation in a tail-recursive way. Therefore, when the attribute is applied, no warning is being produced.<\/p>\n<pre><code class=\"language-fsharp\">\r\n[&lt;TailCall&gt;]\r\nlet rec factorialClassic n =\r\n    match n with\r\n    | 0u | 1u -&gt; 1u\r\n    | _ -&gt; n * (factorialClassic (n - 1u))\r\n\/\/ This produces a warning\r\n\r\n[&lt;TailCall&gt;]\r\nlet rec factorialWithAcc n accumulator = \r\n    match n with\r\n    | 0u | 1u -&gt; accumulator\r\n    | _ -&gt; factorialWithAcc (n - 1u) (n * accumulator)\r\n\/\/ This is a tail call and does NOT produce a warning\r\n<\/code><\/pre>\n<h3>Diagnostics on static classes<\/h3>\n<p>F# does not have a dedicated set of keywords for creating a <code>static class<\/code>.\nHowever, types that are sealed cannot be inherited, and types which are abstract cannot be instantiated.\nThis in practice means that only static members can be accessed on such type.<\/p>\n<p>In order to eliminate runtime errors and dead code, a suite of new warnings for detecting invalid scenarios has been created.\nThe following diagnostics have been added as warnings enabled for F# 8:<\/p>\n<p><strong>New warnings emitted:<\/strong><\/p>\n<p>If a type uses both [&lt;Sealed&gt;] and [&lt;AbstractClass&gt;] attributes, it means it is static. This means:<\/p>\n<ul>\n<li>Instance let bindings are not allowed.<\/li>\n<li>Implementing interfaces is not allowed.<\/li>\n<li>Explicit field declarations are not allowed.<\/li>\n<li>Constructor with arguments is not allowed.<\/li>\n<li>Additional constructor is not allowed.<\/li>\n<li>Abstract member declarations are not allowed.<\/li>\n<\/ul>\n<h3>Diagnostics on <code>[&lt;Obsolete&gt;]<\/code> usage<\/h3>\n<p>The <code>[&lt;Obsolete&gt;]<\/code> attribute can be used to mark types and members not recommended for usage.\nThe compiler should then emit a warning with a user-defined message (the message is taken from the attribute&#8217;s content) when a usage is being detected.<\/p>\n<p>The diagnostics around obsolete members have received the following support with F# 8:<\/p>\n<ul>\n<li>Detection on enum value usage<\/li>\n<li>Detection on an event<\/li>\n<li>Detection on record copy-and-update syntax (<code>with<\/code>) only when the obsolete field is part of the update<\/li>\n<\/ul>\n<p><strong>Example of code which now produces a warning<\/strong><\/p>\n<pre><code class=\"language-fsharp\">open System\r\ntype Color =\r\n    | [&lt;Obsolete(\"Use B instead\")&gt;] Red = 0\r\n    | Green = 1\r\n\r\nlet c = Color.Red \/\/ warning \"This construct is deprecated. Use B instead\" at this line\r\n<\/code><\/pre>\n<h3>Optional warning when <code>obj<\/code> is inferred<\/h3>\n<p>F# has a strong <a href=\"https:\/\/learn.microsoft.com\/dotnet\/fsharp\/language-reference\/type-inference\">type inference<\/a> on both parameter and return types.\nFor consumption of certain APIs, it can happen that <a href=\"https:\/\/learn.microsoft.com\/dotnet\/fsharp\/language-reference\/type-inference#automatic-generalization\">automatic generalization<\/a> fails to infer a value as a generic, and type is instead infered as <code>obj<\/code>.<\/p>\n<p>Most of the times (not always), this is an indication of an unintended action.\nF# 8 brings in a new optional information-level diagnostics with the number FS3559 and text:\n<code>\"A type has been implicitly inferred as 'obj', which may be unintended. Consider adding explicit type annotations. You can disable this warning by using '#nowarn \\\"3559\\\"' or '--nowarn:3559'.\"<\/code><\/p>\n<p>Example of code that can trigger it:<\/p>\n<pre><code class=\"language-fsharp\">([] = [])\r\n<\/code><\/pre>\n<p>This warning is off by default, meaning it has to be explicitely enabled using <code>&lt;WarnOn&gt;FS3559&lt;\/WarnOn&gt;<\/code> in your .fsproj project file.<\/p>\n<h3>Optional warning when copy and update changes all fields<\/h3>\n<p>F# records offer a handy syntax to create a copy with selected fields modified, using the <code>with<\/code> keyword.\nThe project can be configured (via <code>&lt;WarnOn&gt;FS3560&lt;\/WarnOn&gt;<\/code>) to detect a situation in which copying syntax happens to change all fields of a record.\nIn such cases, it will be shorter and more efficient to create a new record from scratch, and populate all the fields directly.\nThe compiler will then report a warning saying:\n<code>\"This copy-and-update record expression changes all fields of record type '..name of your type..'. Consider using the record construction syntax instead.\"<\/code><\/p>\n<h2>Quality of life improvements<\/h2>\n<p>F# 8 also brings in many quality of life improvements, not related to new language features nor new diagnostics.\nHere are a few selected ones:<\/p>\n<h3>Trimmability for compiler-generated code<\/h3>\n<p>.NET platform supports <a href=\"https:\/\/learn.microsoft.com\/dotnet\/core\/deploying\/trimming\/trim-self-contained\">trimming<\/a> since .NET 6. 3 improvements have been done to better support for trimming F#-compiler-generated code:<\/p>\n<ul>\n<li>Discriminated unions are now trimmable.<\/li>\n<li>Anonymous records are now trimmable.<\/li>\n<li>Code using <code>printfn \"%A\"<\/code> for trimmed records is now trimmable.<\/li>\n<\/ul>\n<h3>Parser recovery<\/h3>\n<p>Parser recovery comes to play when the F# parser encounters an invalid code construct, but should still attempt to continue parsing the rest of the file at a best-attempt basis.<\/p>\n<p>This ensures that IDE features like coloring and navigation keep working even for code with syntactical errors or code which is being typed and is not finished yet. Mistakes like missing equals signs, unfinished declarations or incorrect indentation have received vastly improved parser recovery; bringing a better typing experience to F# users.<\/p>\n<p>It very likely is the area of most improvements in F# 8 considering the number of pull requests targetting it.<\/p>\n<p><a href=\"https:\/\/amplifying-fsharp.github.io\/sessions\/2023\/07\/07\/\">Watch this video session<\/a> to learn more about parser recovery in depth.<\/p>\n<h3>Strict indentation rules<\/h3>\n<p>As part of work needed for improved parser recovery, F# 8 turns on a strict indentation mode. This mode respects the rules of the language for indentation and reports an error in invalid scenarios where the previous language versions reported a warning only.<\/p>\n<p>Projects targetting F# language version 7 or lower keep the current behavior, projects for F# 8 and newer have the strict indentation rules turned on automatically.<\/p>\n<p>The default selection can be configured using a compiler switch:<\/p>\n<p><code>--strict-indentation[+|-] Override indentation rules implied by the language version<\/code><\/p>\n<p>This can be also turned off in the project file, by specifying:<\/p>\n<p><code>&lt;OtherFlags&gt;--strict-indentation-&lt;\/..&gt;<\/code> to turn this feature off or<\/p>\n<p><code>&lt;OtherFlags&gt;--strict-indentation+&lt;\/..&gt;<\/code> to turn it on.<\/p>\n<h3>Autocomplete improvements<\/h3>\n<p>The F# compiler provides a library which brings in autocomplete logic for F# code to your favourite editors, such as Visual Studio, Visual Studio Code or Rider.<\/p>\n<p>The improvements cover both recall and precision &#8211; suggesting names which are needed in the given context, and not offering the entities which cannot be used there.<\/p>\n<p>The following completion scenarios have been improved:<\/p>\n<ul>\n<li>Record completions in patterns<\/li>\n<li>Union fields in patterns<\/li>\n<li>Return type annotations<\/li>\n<li>Method completions for overrides<\/li>\n<li>Constant value completions in pattern matching (e.g. matching against constant in System.Double type)<\/li>\n<li>Expressions in enum values<\/li>\n<li>Suggesting names based on labels of union-case fields if they are defined<\/li>\n<li>Completions for collections of anonymous records<\/li>\n<li>Settable properties in attribute completions<\/li>\n<\/ul>\n<h3><code>[&lt;Struct&gt;]<\/code> unions can now have &gt; 49 cases<\/h3>\n<p>Many issues have been resolved as part of F# 8 and they will not all be mentioned in this blog post. One I&#8217;d like to mention was a limiting factor for F# codebases:<\/p>\n<p>Before F# 8, declarations of struct unions with more than 49 cases caused an unexpected runtime error. As a workaround, they typically had to be converted to regular class-based unions.<\/p>\n<p>With F# 8, this limitation is removed and struct unions do not come with a size limitation &#8211; making them suitable for longer definitions of union cases. Examples might be unions of countries or phone codes.<\/p>\n<h2>Compiler performance<\/h2>\n<p>Compiler performance is a big topic for the F# compiler and related tooling. The compiler is what powers independent builds, language features in IDEs as well as popular libraries built on top of it (e.g. <a href=\"https:\/\/fsprojects.github.io\/fantomas\/docs\/index.html\">Fantomas<\/a>, the F# code formatter).<\/p>\n<p>Two areas have received special attention in this release of F# &#8211; incremental builds of large graphs of projects via the <code>Reference assemblies<\/code> feature, and CPU-parallelization of the compiler process.<\/p>\n<h3>Reference assemblies<\/h3>\n<p><a href=\"https:\/\/learn.microsoft.com\/dotnet\/standard\/assembly\/reference-assemblies\">Reference assemblies<\/a> are special assemblies in .NET which can be used in design-time and build-time scenarios.<\/p>\n<p>They work by keeping the public shape of an API (surface area), but stripping out implementation details and replacing them with a <code>\"throw null\"<\/code> instruction. That way, they get smaller and, more importantly, more robust to change. If the implementation details in the real project change, chances are that the reference assembly remains the same as long as APIs have not changed.<\/p>\n<p>This is an existing feature which was already working for F#, but not utilized to its potential due to F#&#8217;s compiler-generated resources. F# compiler enriches produces assemblies with embedded resources, binary data representing the F# signature information and F# optimizer for cross-assembly F# support and cross-assembly function inlining.<\/p>\n<p>For the purpose of reference assemblies, optimization data has been reduced to <code>\"let inline\"<\/code> definitions only in DEBUG builds, and F# signature data has been replaced with a custom hash function covering the public surface of F# types, modules and namespaces.<\/p>\n<p>With those two changes, changes of implementation details in a low-level F# project will not require a full rebuild of all the projects transitively depending on it. This can be a big factor especially in a large solutions with interconnected projects.<\/p>\n<p>When using Visual Studio, the benefit of reference assemblies can go even further by avoiding the need to call into <code>msbuild.exe<\/code> alltogether, by replicating up-to-date checks in VisualStudio. This <a href=\"https:\/\/devblogs.microsoft.com\/visualstudio\/vs-toolbox-accelerate-your-builds-of-sdk-style-net-projects\/\">blog about AccelerateBuildsInVisualStudio<\/a> explains the feature in bigger details.<\/p>\n<p>If you want to give it a try, add the following to your .fsproj project properties:<\/p>\n<ul>\n<li><code>&lt;AccelerateBuildsInVisualStudio&gt;true&lt;\/..&gt;<\/code><\/li>\n<\/ul>\n<h3>Switches for compiler parallelization<\/h3>\n<p>Second area of improvements I want to mention is compiler parallelization.\nHistorically, the F# compiler has been single-threaded. In recent F# versions, parallelization has been enabled for parsing and for typechecking implementation files backed by signature files.<\/p>\n<p>F# 8 brings in 3 new experimental features that have been added to cover 3 other stages of the F# compilation process.\nAt the moment, they are not enabled by default and are only configurable via dedicated <code>--test:<\/code> flags to the command line compiler. They can be also passed to the compiler based on project file, using the following property syntax for <a href=\"https:\/\/learn.microsoft.com\/dotnet\/fsharp\/language-reference\/compiler-options\">F# compiler options<\/a>:<\/p>\n<ul>\n<li><code>&lt;OtherFlags&gt;--test:..&lt;\/..&gt;<\/code><\/li>\n<\/ul>\n<p>Graph-based typechecking has the potential of speeding up the build process the most. It works by deriving dependencies of F# files from the untyped syntax tree before the expensive typechecking phase, and then controlling which files can be typechecked in parallel by following the connected graph of files.<\/p>\n<p>This feature has been demonstrated in an <a href=\"https:\/\/amplifying-fsharp.github.io\/\">Amplifying F#<\/a> session on <a href=\"https:\/\/amplifying-fsharp.github.io\/sessions\/2023\/04\/28\/\">Graph-based type-checking<\/a> and is described in <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/a-new-fsharp-compiler-feature-graphbased-typechecking\/\">this blog post<\/a>.<\/p>\n<p>This can be turned on by the flag:<\/p>\n<ul>\n<li>&#8211;test:GraphBasedChecking<\/li>\n<\/ul>\n<p>Next phase of the compilation process are the optimizations applied to F# code. The technical description of the chosen approach is well described in the <a href=\"https:\/\/github.com\/dotnet\/fsharp\/pull\/14390\">pull request implementing the feature<\/a>.<\/p>\n<p>Timings for <code>FSharp.Compiler.Service<\/code> showing the difference with (Parallel) or without (Sequential) this feature:\nTest run on a 8-core\/16-thread CPU.\nIt&#8217;s worth noting that no more than 7 items are processed at a time.<\/p>\n<table>\n<thead>\n<tr>\n<th>Optimize<\/th>\n<th>Mode<\/th>\n<th>Optimization Time<\/th>\n<th>Total Time<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>+<\/td>\n<td>Sequential<\/td>\n<td>12.9s<\/td>\n<td>31.0s<\/td>\n<\/tr>\n<tr>\n<td>+<\/td>\n<td>Parallel<\/td>\n<td>7.1s (-45%)<\/td>\n<td>25.5s (-18%)<\/td>\n<\/tr>\n<tr>\n<td>&#8211;<\/td>\n<td>Sequential<\/td>\n<td>5.6s<\/td>\n<td>24.3s<\/td>\n<\/tr>\n<tr>\n<td>&#8211;<\/td>\n<td>Parallel<\/td>\n<td>3.7s (-34%)<\/td>\n<td>23.6s (-3%)<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>The feature can be enabled using the following flag:<\/p>\n<ul>\n<li>&#8211;test:ParallelOptimization<\/li>\n<\/ul>\n<p>Last phase of the F# build process is the code generation of IL instructions and creation of an assembly.\nIn this phase, the parallelization happens by converting methods bodies into .NET IL in parallel.\nFor the main test project in the F# repository, this change lead to an improvement from 0.6s to 0.4s to the IL conversion, therefore a -33% speed improvement.<\/p>\n<p>The feature can be enabled using the following flag:<\/p>\n<ul>\n<li>&#8211;test:ParallelIlxGen<\/li>\n<\/ul>\n<p>Alternatively, all of the features can be enabled globally using an <code>FSHARP_EXPERIMENTAL_FEATURES<\/code> environment variable, for example like this in PowerShell: <code>$env:FSHARP_EXPERIMENTAL_FEATURES = '1<\/code>.<\/p>\n<h2>Enhancements to the FSharp.Core standard library<\/h2>\n<h3>Inlining<\/h3>\n<p>F# already has a powerful feature called inlining.\nInstead of calling a function directly, the compiler can decide to put the body of the called function (=inline it) into the call site. That way, not only is a function call being eliminated, but the compiler can also optimize further based on type arguments and function arguments available at the call site. For example, a regular generic function for equality would have to go via standard interfaces and contracts (e.g., the <code>.Equals()<\/code> method). When inlining the generic usage in a call site that uses it on integers, it can instead replace it with a single instruction that compares two integers.<\/p>\n<p>Furthermore, F# also has the <code>[&lt;InlineIfLambda&gt;]<\/code> attribute which allows to replace a lambda function with a direct call when inlining happens. On top of all the previous advantages, this also means a saved allocation for the closure of the lambda.<\/p>\n<p>F# 8 comes with inlining changes to two standard modules in FSharp.Core:<\/p>\n<ul>\n<li><a href=\"https:\/\/github.com\/dotnet\/fsharp\/pull\/14927\">Inlining<\/a> for functions in the <code>Option<\/code> module<\/li>\n<li><a href=\"https:\/\/github.com\/dotnet\/fsharp\/pull\/15709\">Inlining<\/a> for functions in the <code>ValueOption<\/code> module<\/li>\n<\/ul>\n<p>Lambda allocations have been removed, which in the case of <code>ValueOption<\/code> functions means zero allocations alltogether.\nAs an example of the reduced overhead, mapping of the <code>None<\/code> value now takes 0.17ns instead of 2.77ns, which is a 16x improvement in terms of time.<\/p>\n<p>Another example is an equality-inlining issue which has been spotted at <a href=\"https:\/\/github.com\/dotnet\/fsharp\/issues\/15720\">this issue about List.contains performance<\/a>.\nThe reason was a stopped inlining because of recursive scope &#8211; when a function is recursive, it cannot be inlined (inlining effectively means embedding the code of the inlined function at the call site &#8211; which for a recursive function would mean including it infinitely many times).\nOnce the equality check was moved outside of the recursive scope, equality check can be inlined and properly special-cased.<\/p>\n<p><a href=\"https:\/\/github.com\/dotnet\/fsharp\/pull\/15726\/files#diff-3564806fd5c409bdb92242766794a120c6897330a21c0b12ce2138c2e6aa44ecR462\">This is the minimal code change<\/a> which improved it, leading up to an 16x improvement in certain scenarios.<\/p>\n<table>\n<thead>\n<tr>\n<th>Method<\/th>\n<th style=\"text-align: right\">Mean<\/th>\n<th style=\"text-align: right\">Error<\/th>\n<th style=\"text-align: right\">StdDev<\/th>\n<th style=\"text-align: right\">Median<\/th>\n<th style=\"text-align: right\">Gen0<\/th>\n<th style=\"text-align: right\">Allocated<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>&#8216;int &#8211; List.containsOld&#8217;<\/td>\n<td style=\"text-align: right\">9,284.4 \u03bcs<\/td>\n<td style=\"text-align: right\">158.63 \u03bcs<\/td>\n<td style=\"text-align: right\">148.38 \u03bcs<\/td>\n<td style=\"text-align: right\">9,266.7 \u03bcs<\/td>\n<td style=\"text-align: right\">1906.2500<\/td>\n<td style=\"text-align: right\">24024049 B<\/td>\n<\/tr>\n<tr>\n<td>&#8216;int &#8211; List.containsNew&#8217;<\/td>\n<td style=\"text-align: right\">548.4 \u03bcs<\/td>\n<td style=\"text-align: right\">10.35 \u03bcs<\/td>\n<td style=\"text-align: right\">15.17 \u03bcs<\/td>\n<td style=\"text-align: right\">541.9 \u03bcs<\/td>\n<td style=\"text-align: right\">&#8211;<\/td>\n<td style=\"text-align: right\">41 B<\/td>\n<\/tr>\n<tr>\n<td>&#8216;string &#8211; List.containsOld&#8217;<\/td>\n<td style=\"text-align: right\">2,393.9 \u03bcs<\/td>\n<td style=\"text-align: right\">43.59 \u03bcs<\/td>\n<td style=\"text-align: right\">83.98 \u03bcs<\/td>\n<td style=\"text-align: right\">2,359.5 \u03bcs<\/td>\n<td style=\"text-align: right\">&#8211;<\/td>\n<td style=\"text-align: right\">42 B<\/td>\n<\/tr>\n<tr>\n<td>&#8216;string &#8211; List.containsNew&#8217;<\/td>\n<td style=\"text-align: right\">838.6 \u03bcs<\/td>\n<td style=\"text-align: right\">21.62 \u03bcs<\/td>\n<td style=\"text-align: right\">62.38 \u03bcs<\/td>\n<td style=\"text-align: right\">824.1 \u03bcs<\/td>\n<td style=\"text-align: right\">&#8211;<\/td>\n<td style=\"text-align: right\">41 B<\/td>\n<\/tr>\n<tr>\n<td>&#8216;record &#8211; List.containsOld&#8217;<\/td>\n<td style=\"text-align: right\">3,796.8 \u03bcs<\/td>\n<td style=\"text-align: right\">66.80 \u03bcs<\/td>\n<td style=\"text-align: right\">113.44 \u03bcs<\/td>\n<td style=\"text-align: right\">3,753.0 \u03bcs<\/td>\n<td style=\"text-align: right\">&#8211;<\/td>\n<td style=\"text-align: right\">45 B<\/td>\n<\/tr>\n<tr>\n<td>&#8216;record &#8211; List.containsNew&#8217;<\/td>\n<td style=\"text-align: right\">691.4 \u03bcs<\/td>\n<td style=\"text-align: right\">13.61 \u03bcs<\/td>\n<td style=\"text-align: right\">16.20 \u03bcs<\/td>\n<td style=\"text-align: right\">688.9 \u03bcs<\/td>\n<td style=\"text-align: right\">&#8211;<\/td>\n<td style=\"text-align: right\">41 B<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3>Improvements<\/h3>\n<h3><code>Array.Parallel.*<\/code> APIs<\/h3>\n<p>Fsharp.Core offers many standard functions for major collection types &#8211; arrays, lists and sequences.\nArray contains a nested module, Array.Parallel, intended for CPU-intensive tasks over large arrays. Before F# 8, that module was not on par with the non-parallel functions, and was missing many basic functions. With this release, many new APIs have been added to support multi-threaded programming.<\/p>\n<p>Each of the functions will try to consume all the logical processors available to it (this can be fine-tuned via the <code>DOTNET_PROCESSOR_COUNT<\/code> environment variable if the default is not desired).\nThey are especially useful in CPU-intensive processing. That is, either when the arrays are very large, when the function parameter passed in is itself computationally expensive, or both.\nThey do carry the overhead of creating new tasks and coordinating them, therefore can be slower then their non-parallel counterparts for simple inputs.<\/p>\n<ul>\n<li>exists<\/li>\n<li>forAll<\/li>\n<li>tryFindIndex<\/li>\n<li>tryFind<\/li>\n<li>tryPick<\/li>\n<li>reduceBy<\/li>\n<li>reduce<\/li>\n<li>minBy<\/li>\n<li>min<\/li>\n<li>sumBy<\/li>\n<li>sum<\/li>\n<li>maxBy<\/li>\n<li>max<\/li>\n<li>averageBy<\/li>\n<li>average<\/li>\n<li>zip<\/li>\n<li>groupBy<\/li>\n<li>filter<\/li>\n<li>sortInPlaceWith<\/li>\n<li>sortInPlaceBy<\/li>\n<li>sortInPlace<\/li>\n<li>sortWith<\/li>\n<li>sortBy<\/li>\n<li>sort<\/li>\n<li>sortByDescending<\/li>\n<li>sortDescending\n<h4>Selected benchmarks<\/h4>\n<p>To demonstrate the difference between <code>Array<\/code>, <code>Array.Parallel<\/code> and <code>PLINQ<\/code>, several benchmarks are included in this blog post.\nAll of these were running for an array of <strong>500.000 randomly generated structs<\/strong> using the following definition of the struct, and an artificial function representing <em>&#8220;complex CPU-intensive business logic&#8221;<\/em>.<\/li>\n<\/ul>\n<p><strong>Possible now:<\/strong><\/p>\n<pre><code class=\"language-fsharp\">[&lt;Struct&gt;]\r\ntype SampleRecord = {Age : int; Balance : int64; Molecules : float; IsMiddle : bool}\r\n\r\nlet complexLogic (sr:SampleRecord) = \r\n    let mutable total = float sr.Balance\r\n    total &lt;- total + sin sr.Molecules\r\n    total &lt;- atan total\r\n    for a=0 to sr.Age do\r\n        total &lt;- total + cos (float a)\r\n    total &lt;- total + float (hash sr)\r\n    total\r\n <\/code><\/pre>\n<p>This was executed on a 11th Gen Intel Core i9-11950H 2.60GHz machine, with 16 logical and 8 physical cores.\nThe comparisons were done across three different approaches:<\/p>\n<ul>\n<li>Array module from FSharp.Core<\/li>\n<li>PLINQ (Parallel Enumerable via <code>AsParallel()<\/code>), a stable and robust implementation from .NET<\/li>\n<li>Newly added functions to the <code>Array.Parallel<\/code> moduleThe major outcome for the comparison is the classical <em>&#8220;it depends&#8221;<\/em> consulting advice.\n500.000 elements can still be processed sequentially very fast (notice we are in the <code>ms<\/code> time range at most) and in cases of trivial calculation (e.g. just a property\/field accessor, and not an expensive lambda function), the non-parallelized functions from the <code>Array<\/code> module perform the best. See for example the &#8216;GroupBy &#8211; field only&#8217;, &#8216;Sort &#8211; by int field&#8217; and &#8216;SumBy(plain field access)&#8217; categories in the results.The parallel versions start to get an advantage when the calculation to be invoked on every element gets more complex, like the <code>complexLogic<\/code> function above. In those cases, the <code>Array.Parallel<\/code> module brings both faster speed compared to the classical version, as well as better allocation footprint compared to PLINQ.A very significant performance improvement occurs at the <code>MinBy<\/code> calculation, where the <code>Array.Parallel.minBy<\/code> version is 68% faster than <code>Array.minBy<\/code> and 70% faster than <code>AsParallel().MinBy(..)<\/code> from PLINQ. MinBy is an example of an aggregation function, just like max, sum or average. They are all built on a lower-level <code>reduce<\/code> function utilizing a map-reduce routine, and very similar performance is expected across them all.<\/li>\n<\/ul>\n<table>\n<thead>\n<tr>\n<th>Method<\/th>\n<th>Categories<\/th>\n<th style=\"text-align: right\">Mean<\/th>\n<th style=\"text-align: right\">Ratio<\/th>\n<th style=\"text-align: right\">Allocated<\/th>\n<th style=\"text-align: right\">Alloc Ratio<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>ArrayGroupBy2<\/td>\n<td>GroupBy &#8211; calculation<\/td>\n<td style=\"text-align: right\">169,024.8 us<\/td>\n<td style=\"text-align: right\">baseline<\/td>\n<td style=\"text-align: right\">70.17 MB<\/td>\n<td style=\"text-align: right\"><\/td>\n<\/tr>\n<tr>\n<td>PlinqGroupBy2<\/td>\n<td>GroupBy &#8211; calculation<\/td>\n<td style=\"text-align: right\">74,683.8 us<\/td>\n<td style=\"text-align: right\">-56%<\/td>\n<td style=\"text-align: right\">103.93 MB<\/td>\n<td style=\"text-align: right\">+48%<\/td>\n<\/tr>\n<tr>\n<td>ArrayParallelGroupBy2<\/td>\n<td>GroupBy &#8211; calculation<\/td>\n<td style=\"text-align: right\">62,574.3 us<\/td>\n<td style=\"text-align: right\">-63%<\/td>\n<td style=\"text-align: right\">70.61 MB<\/td>\n<td style=\"text-align: right\">+1%<\/td>\n<\/tr>\n<tr>\n<td><\/td>\n<td><\/td>\n<td style=\"text-align: right\"><\/td>\n<td style=\"text-align: right\"><\/td>\n<td style=\"text-align: right\"><\/td>\n<td style=\"text-align: right\"><\/td>\n<\/tr>\n<tr>\n<td>ArrayGroupBy<\/td>\n<td>GroupBy &#8211; field only<\/td>\n<td style=\"text-align: right\">14,274.3 us<\/td>\n<td style=\"text-align: right\">baseline<\/td>\n<td style=\"text-align: right\">57.28 MB<\/td>\n<td style=\"text-align: right\"><\/td>\n<\/tr>\n<tr>\n<td>PlinqGroupBy<\/td>\n<td>GroupBy &#8211; field only<\/td>\n<td style=\"text-align: right\">30,933.6 us<\/td>\n<td style=\"text-align: right\">+117%<\/td>\n<td style=\"text-align: right\">88.77 MB<\/td>\n<td style=\"text-align: right\">+55%<\/td>\n<\/tr>\n<tr>\n<td>ArrayParallelGroupBy<\/td>\n<td>GroupBy &#8211; field only<\/td>\n<td style=\"text-align: right\">18,318.6 us<\/td>\n<td style=\"text-align: right\">+29%<\/td>\n<td style=\"text-align: right\">47.72 MB<\/td>\n<td style=\"text-align: right\">-17%<\/td>\n<\/tr>\n<tr>\n<td><\/td>\n<td><\/td>\n<td style=\"text-align: right\"><\/td>\n<td style=\"text-align: right\"><\/td>\n<td style=\"text-align: right\"><\/td>\n<td style=\"text-align: right\"><\/td>\n<\/tr>\n<tr>\n<td>ArrayMinBy<\/td>\n<td>MinBy(calculationFunction)<\/td>\n<td style=\"text-align: right\">157,463.5 us<\/td>\n<td style=\"text-align: right\">baseline<\/td>\n<td style=\"text-align: right\">11.44 MB<\/td>\n<td style=\"text-align: right\"><\/td>\n<\/tr>\n<tr>\n<td>PlinqMinBy<\/td>\n<td>MinBy(calculationFunction)<\/td>\n<td style=\"text-align: right\">160,243.5 us<\/td>\n<td style=\"text-align: right\">+2%<\/td>\n<td style=\"text-align: right\">11.44 MB<\/td>\n<td style=\"text-align: right\">+0%<\/td>\n<\/tr>\n<tr>\n<td>ArrayParallelMinBy<\/td>\n<td>MinBy(calculationFunction)<\/td>\n<td style=\"text-align: right\">48,768.7 us<\/td>\n<td style=\"text-align: right\">-68%<\/td>\n<td style=\"text-align: right\">11.45 MB<\/td>\n<td style=\"text-align: right\">+0%<\/td>\n<\/tr>\n<tr>\n<td><\/td>\n<td><\/td>\n<td style=\"text-align: right\"><\/td>\n<td style=\"text-align: right\"><\/td>\n<td style=\"text-align: right\"><\/td>\n<td style=\"text-align: right\"><\/td>\n<\/tr>\n<tr>\n<td>ArraySort<\/td>\n<td>Sort &#8211; by int field<\/td>\n<td style=\"text-align: right\">27,352.1 us<\/td>\n<td style=\"text-align: right\">baseline<\/td>\n<td style=\"text-align: right\">17.17 MB<\/td>\n<td style=\"text-align: right\"><\/td>\n<\/tr>\n<tr>\n<td>PlinqSort<\/td>\n<td>Sort &#8211; by int field<\/td>\n<td style=\"text-align: right\">38,723.7 us<\/td>\n<td style=\"text-align: right\">+42%<\/td>\n<td style=\"text-align: right\">172.89 MB<\/td>\n<td style=\"text-align: right\">+907%<\/td>\n<\/tr>\n<tr>\n<td>ArrayParallelSort<\/td>\n<td>Sort &#8211; by int field<\/td>\n<td style=\"text-align: right\">76,744.8 us<\/td>\n<td style=\"text-align: right\">+179%<\/td>\n<td style=\"text-align: right\">112.76 MB<\/td>\n<td style=\"text-align: right\">+557%<\/td>\n<\/tr>\n<tr>\n<td><\/td>\n<td><\/td>\n<td style=\"text-align: right\"><\/td>\n<td style=\"text-align: right\"><\/td>\n<td style=\"text-align: right\"><\/td>\n<td style=\"text-align: right\"><\/td>\n<\/tr>\n<tr>\n<td>ArraySortBy<\/td>\n<td>SortBy &#8211; calculation<\/td>\n<td style=\"text-align: right\">214,042.4 us<\/td>\n<td style=\"text-align: right\">baseline<\/td>\n<td style=\"text-align: right\">30.52 MB<\/td>\n<td style=\"text-align: right\"><\/td>\n<\/tr>\n<tr>\n<td>PlinqSortBy<\/td>\n<td>SortBy &#8211; calculation<\/td>\n<td style=\"text-align: right\">97,214.3 us<\/td>\n<td style=\"text-align: right\">-55%<\/td>\n<td style=\"text-align: right\">193.99 MB<\/td>\n<td style=\"text-align: right\">+536%<\/td>\n<\/tr>\n<tr>\n<td>ArrayParallelSortBy<\/td>\n<td>SortBy &#8211; calculation<\/td>\n<td style=\"text-align: right\">125,951.7 us<\/td>\n<td style=\"text-align: right\">-41%<\/td>\n<td style=\"text-align: right\">130.49 MB<\/td>\n<td style=\"text-align: right\">+328%<\/td>\n<\/tr>\n<tr>\n<td><\/td>\n<td><\/td>\n<td style=\"text-align: right\"><\/td>\n<td style=\"text-align: right\"><\/td>\n<td style=\"text-align: right\"><\/td>\n<td style=\"text-align: right\"><\/td>\n<\/tr>\n<tr>\n<td>ArraySumBy<\/td>\n<td>SumBy(plain field access)<\/td>\n<td style=\"text-align: right\">466.7 us<\/td>\n<td style=\"text-align: right\">baseline<\/td>\n<td style=\"text-align: right\">&#8211;<\/td>\n<td style=\"text-align: right\">NA<\/td>\n<\/tr>\n<tr>\n<td>PlinqSumBy<\/td>\n<td>SumBy(plain field access)<\/td>\n<td style=\"text-align: right\">984.1 us<\/td>\n<td style=\"text-align: right\">+112%<\/td>\n<td style=\"text-align: right\">0.01 MB<\/td>\n<td style=\"text-align: right\">NA<\/td>\n<\/tr>\n<tr>\n<td>ArrayParallelSumBy<\/td>\n<td>SumBy(plain field access)<\/td>\n<td style=\"text-align: right\">687.6 us<\/td>\n<td style=\"text-align: right\">+47%<\/td>\n<td style=\"text-align: right\">0.01 MB<\/td>\n<td style=\"text-align: right\">NA<\/td>\n<\/tr>\n<tr>\n<td><\/td>\n<td><\/td>\n<td style=\"text-align: right\"><\/td>\n<td style=\"text-align: right\"><\/td>\n<td style=\"text-align: right\"><\/td>\n<td style=\"text-align: right\"><\/td>\n<\/tr>\n<tr>\n<td>ArrayTryFind<\/td>\n<td>TryFind &#8211; calculationFunction<\/td>\n<td style=\"text-align: right\">76,509.7 us<\/td>\n<td style=\"text-align: right\">baseline<\/td>\n<td style=\"text-align: right\">5.72 MB<\/td>\n<td style=\"text-align: right\"><\/td>\n<\/tr>\n<tr>\n<td>PlinqTryFind<\/td>\n<td>TryFind &#8211; calculationFunction<\/td>\n<td style=\"text-align: right\">41,256.7 us<\/td>\n<td style=\"text-align: right\">-47%<\/td>\n<td style=\"text-align: right\">10.74 MB<\/td>\n<td style=\"text-align: right\">+88%<\/td>\n<\/tr>\n<tr>\n<td>ArrayParallelTryFind<\/td>\n<td>TryFind &#8211; calculationFunction<\/td>\n<td style=\"text-align: right\">23,094.4 us<\/td>\n<td style=\"text-align: right\">-69%<\/td>\n<td style=\"text-align: right\">5.73 MB<\/td>\n<td style=\"text-align: right\">+0%<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3><code>async<\/code> improvements<\/h3>\n<ul>\n<li><code>Bind<\/code> of <code>Async&lt;&gt;<\/code> within <code>task{}<\/code> <a href=\"https:\/\/github.com\/dotnet\/fsharp\/pull\/14499\">now starts on the same thread<\/a>\n<ul>\n<li>This saves resources by keeping the computation on the same .NET thread and not starting a new one<\/li>\n<\/ul>\n<\/li>\n<li><code>MailBoxProcessor<\/code> <a href=\"https:\/\/github.com\/dotnet\/fsharp\/pull\/14929\">now comes<\/a> with a public <code>.Dispose()<\/code> member\n<ul>\n<li>Better discoverability that <code>MailboxProcessor<\/code> implements <code>IDisposable<\/code> and thus must be disposed<\/li>\n<li>Removes need to manually cast to <code>IDisposable<\/code>, as in <code>(mailboxProcessor :&gt; IDisposable).Dispose()<\/code> before disposing<\/li>\n<\/ul>\n<\/li>\n<li><code>MailBoxProcessor<\/code> <a href=\"https:\/\/github.com\/dotnet\/fsharp\/pull\/14931\">now comes<\/a> with <code>StartImmediate<\/code>\n<ul>\n<li>Existing <code>Start<\/code> method starts the execution on the thread pool. The new <code>StartImmediate<\/code> ensures starting on the same thread as the calling one.<\/li>\n<li>This is useful for situations in which you want to force the MailboxProcessor to start on a given thread or even run on a single thread throughout its lifetime. For example, a <code>MailboxProcessor<\/code> may be desired to wrap some unmanaged state, such as a window or some communication session that is not thread safe, and this is not currently possible with <code>MailboxProcessor.Start<\/code>.\n<h2>Thanks and acknowledgements<\/h2>\n<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>F# is developed as a collaboration between the .NET Foundation, the F# Software Foundation, their members and other contributors including Microsoft. The F# community is involved at all stages of innovation, design, implementation and delivery and we\u2019re proud to be a contributing part of this community.<\/p>\n<p>Between October 2022 and October 2023, the <a href=\"https:\/\/github.com\/dotnet\/fsharp\/graphs\/contributors?from=2022-10-01&amp;to=2023-10-01&amp;type=c\">dotnet\/fsharp<\/a> repository tracks 33 contributors with commits in that period. On top of that, there are numerous contributors filing <a href=\"https:\/\/github.com\/dotnet\/fsharp\">issues<\/a>, <a href=\"https:\/\/github.com\/fsharp\/fslang-suggestions\">raising and voting on suggestions<\/a> and contributing to <a href=\"https:\/\/github.com\/fsharp\/fslang-design\">feature design<\/a>. Thank you all!<\/p>\n<p>Below is a wall created from the profile pictures listed at GitHub&#8217;s <a href=\"https:\/\/github.com\/dotnet\/fsharp\/graphs\/contributors?from=2022-10-01&amp;to=2023-10-01&amp;type=c\">automated statistics<\/a> for the <a href=\"https:\/\/github.com\/dotnet\/fsharp\">dotnet\/fsharp<\/a> repository over the F# 8 development period. Visit the link and see all the contributors including the changes they have brought to F#. Order of the profile pictures here is ASCII alphabetical.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/avatars.githubusercontent.com\/u\/217092?s=60&amp;v=4\" alt=\"0101\" \/>\n<img decoding=\"async\" src=\"https:\/\/avatars.githubusercontent.com\/u\/26364714?s=60&amp;v=4\" alt=\"DedSec256\" \/>\n<img decoding=\"async\" src=\"https:\/\/avatars.githubusercontent.com\/u\/19922066?s=60&amp;v=4\" alt=\"Happypig375\" \/>\n<img decoding=\"async\" src=\"https:\/\/avatars.githubusercontent.com\/u\/5175830?s=60&amp;v=4\" alt=\"KevinRansom\" \/>\n<img decoding=\"async\" src=\"https:\/\/avatars.githubusercontent.com\/u\/7862010?s=60&amp;v=4\" alt=\"MattGal\" \/>\n<img decoding=\"async\" src=\"https:\/\/avatars.githubusercontent.com\/u\/9423618?s=60&amp;v=4\" alt=\"NikolaMilosavljevic\" \/>\n<img decoding=\"async\" src=\"https:\/\/avatars.githubusercontent.com\/u\/4218809?s=60&amp;v=4\" alt=\"NinoFloris\" \/>\n<img decoding=\"async\" src=\"https:\/\/avatars.githubusercontent.com\/u\/3138005?s=60&amp;v=4\" alt=\"Smaug123\" \/>\n<img decoding=\"async\" src=\"https:\/\/avatars.githubusercontent.com\/u\/46543583?s=60&amp;v=4\" alt=\"T-Gro\" \/>\n<img decoding=\"async\" src=\"https:\/\/avatars.githubusercontent.com\/u\/20281641?s=60&amp;v=4\" alt=\"abonie\" \/>\n<img decoding=\"async\" src=\"https:\/\/avatars.githubusercontent.com\/u\/8275461?s=60&amp;v=4\" alt=\"alfonsogarciacaro\" \/>\n<img decoding=\"async\" src=\"https:\/\/avatars.githubusercontent.com\/u\/3923587?s=60&amp;v=4\" alt=\"auduchinok\" \/>\n<img decoding=\"async\" src=\"https:\/\/avatars.githubusercontent.com\/u\/65685447?s=60&amp;v=4\" alt=\"bmitc\" \/>\n<img decoding=\"async\" src=\"https:\/\/avatars.githubusercontent.com\/u\/6309070?s=60&amp;v=4\" alt=\"cartermp\" \/>\n<img decoding=\"async\" src=\"https:\/\/avatars.githubusercontent.com\/u\/3221269?s=60&amp;v=4\" alt=\"dawedawe\" \/>\n<img decoding=\"async\" src=\"https:\/\/avatars.githubusercontent.com\/u\/7204669?s=60&amp;v=4\" alt=\"dsyme\" \/>\n<img decoding=\"async\" src=\"https:\/\/avatars.githubusercontent.com\/u\/31915729?s=60&amp;v=4\" alt=\"edgarfgp\" \/>\n<img decoding=\"async\" src=\"https:\/\/avatars.githubusercontent.com\/u\/884885?s=60&amp;v=4\" alt=\"goswinr\" \/>\n<img decoding=\"async\" src=\"https:\/\/avatars.githubusercontent.com\/u\/1261319?s=60&amp;v=4\" alt=\"gusty\" \/>\n<img decoding=\"async\" src=\"https:\/\/avatars.githubusercontent.com\/u\/4031185?s=60&amp;v=4\" alt=\"jwosty\" \/>\n<img decoding=\"async\" src=\"https:\/\/avatars.githubusercontent.com\/u\/4257079?s=60&amp;v=4\" alt=\"kant2002\" \/>\n<img decoding=\"async\" src=\"https:\/\/avatars.githubusercontent.com\/u\/5063478?s=60&amp;v=4\" alt=\"kerams\" \/>\n<img decoding=\"async\" src=\"https:\/\/avatars.githubusercontent.com\/u\/1760221?s=60&amp;v=4\" alt=\"majocha\" \/>\n<img decoding=\"async\" src=\"https:\/\/avatars.githubusercontent.com\/u\/8725170?s=60&amp;v=4\" alt=\"mmitche\" \/>\n<img decoding=\"async\" src=\"https:\/\/avatars.githubusercontent.com\/u\/2621499?s=60&amp;v=4\" alt=\"nojaf\" \/>\n<img decoding=\"async\" src=\"https:\/\/avatars.githubusercontent.com\/u\/5451366?s=60&amp;v=4\" alt=\"psfinaki\" \/>\n<img decoding=\"async\" src=\"https:\/\/avatars.githubusercontent.com\/u\/94796738?s=60&amp;v=4\" alt=\"rosskuehl\" \/>\n<img decoding=\"async\" src=\"https:\/\/avatars.githubusercontent.com\/u\/2478401?s=60&amp;v=4\" alt=\"safesparrow\" \/>\n<img decoding=\"async\" src=\"https:\/\/avatars.githubusercontent.com\/u\/447391?s=60&amp;v=4\" alt=\"tboby\" \/>\n<img decoding=\"async\" src=\"https:\/\/avatars.githubusercontent.com\/u\/12659251?s=60&amp;v=4\" alt=\"teo-tsirpanis\" \/>\n<img decoding=\"async\" src=\"https:\/\/avatars.githubusercontent.com\/u\/1260985?s=60&amp;v=4\" alt=\"vzarytovskii\" \/><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/avatars.githubusercontent.com\/u\/29605222?s=60&amp;v=4\" width=\"60\" \/>\n<img decoding=\"async\" src=\"https:\/\/avatars.githubusercontent.com\/u\/87944?s=60&amp;v=4\" width=\"60\" \/><\/p>\n<p>We want to thank all contributors, community members and users of F#. On top of that, we want to call out the following heavily contributing members and a selection of their recent work in the <a href=\"https:\/\/github.com\/dotnet\/fsharp\">F# repository<\/a> explicitly:<\/p>\n<ul>\n<li><a href=\"https:\/\/github.com\/kerams\">@kerams<\/a> for the majority of new language features, completion improvements and many other additions to F#.<\/li>\n<li><a href=\"https:\/\/github.com\/auduchinok\">@auduchinok<\/a> for parser recovery improvements, strict indentation mode and many performance improvements in the F# compiler itself.<\/li>\n<li><a href=\"https:\/\/github.com\/nojaf\">@nojaf<\/a> for syntax tree additions, code printing additions, signature files improvements and graph-based typechecking.<\/li>\n<li><a href=\"https:\/\/github.com\/safesparrow\">@safesparrow<\/a> for compiler observability and tracing, graph-based typechecking and parallel optimization.<\/li>\n<li><a href=\"https:\/\/github.com\/majocha\">@majocha<\/a> for improvements in the VS editor support which improve user experience, editor speed and code search experience.<\/li>\n<li><a href=\"https:\/\/github.com\/edgarfgp\">@edgarfp<\/a> for improvements in error handling, user friendliness of existing diagnostics and additions of many new F# diagnostics.<\/li>\n<\/ul>\n<h2>Contributor showcase<\/h2>\n<p>As in our last release, we want to highlight individuals who contribute to F#, and publish a short text about them using their own words.<\/p>\n<h3>dawedawe<\/h3>\n<p>I&#8217;m David Schaefer, aka dawe, living near Cologne, Germany.<\/p>\n<p>I fell in love with functional programming (FP) while being exposed to theoretical computer science during my time at the university.\nBack then, I made my first stab at contributing to open source by maintaining some Haskell packages of OpenBSD.\nAfter university, I wasn&#8217;t able to find an FP job and fell out of package maintainership.\nBut a C# job at least allowed me to stay in touch with FP by using F# in my free time.<\/p>\n<p>Motivated to use F# professionally, I joined an awesome small company (Rhein-Spree) which uses F# quite a lot.\nThat led to small contributions to <a href=\"https:\/\/fsprojects.github.io\/fantomas\/\">Fantomas<\/a> and with the great mentorship of Florian,\nI worked my way up to co-maintainership of it.<\/p>\n<p>The ride really got wild when I joined the G-Research Open Source team in March 2023 to work full time on the F# eco-system.\nIn parallel, we launched the Amplifying F# initiative. Watch our video of <a href=\"https:\/\/www.youtube.com\/watch?v=69VVSnng8TY\">fsharpconf 2023<\/a> to learn more.\nToday, I help maintaining various pieces in the eco-system. I&#8217;m proud I earned the needed trust for that.<\/p>\n<p>With the <a href=\"https:\/\/datascienceinfsharp.com\/\">Data Science in F#<\/a> conference still fresh on my mind, I want to emphasize how much the human side means to me.\nSharing a coffee or a beer with friends from all over the world,\ndiscussing new ideas how to move things forward,\ncelebrating wins together and consoling each other after a loss &#8211; that&#8217;s for a large part why I do this.\nAnd all the green squares on GitHub, of course.<\/p>\n<p><img decoding=\"async\" src=\".\/david.jpg\" alt=\"Dawe\" \/><\/p>\n<h3>auduchinok<\/h3>\n<p>I&#8217;m Eugene Auduchinok, I work on F# support in JetBrains Rider and currently live in Amsterdam.\nI have first met with F# in the university as some of the courses used it. F# worked good for the tasks and was really fun to use.\nWhen Rider was announced, I was happy since I loved working on a Mac and using IntelliJ. The problem was it didn&#8217;t have any support for F#, so I&#8217;ve applied for an internship to try to make it happen. This worked out and now I use it daily and enjoy seeing other people using it too. \ud83d\ude42\nSince then I&#8217;ve had a chance to contribute to various parts of F# tooling, work with wonderful people, and even make some impact on the language design itself.<\/p>\n<p><img decoding=\"async\" src=\".\/eugene.jpg\" alt=\"Eugene\" \/><\/p>\n<h2>What&#8217;s next?<\/h2>\n<p>We continue working on F#: be it the language itself, compiler performance, Visual Studio features and improvements and many other aspects of F#.<\/p>\n<ul>\n<li>See the <a href=\"https:\/\/github.com\/orgs\/dotnet\/projects\/126\/views\/17\">overall work tracking issues<\/a><\/li>\n<li>Do you want to help? There is a <a href=\"https:\/\/github.com\/dotnet\/fsharp\/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22help+wanted%22\">&#8220;help wanted&#8221; issues list<\/a><\/li>\n<li>Getting started with the compiler codebase? We have a curated list of <a href=\"https:\/\/github.com\/dotnet\/fsharp\/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22\">good first issues<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Read what is new in F# 8 &#8211; the language, compiler tooling and FSharp.Core standard library<\/p>\n","protected":false},"author":122898,"featured_media":48925,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[685,636],"tags":[7701,73],"class_list":["post-48815","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-dotnet","category-fsharp","tag-dotnet-8","tag-f"],"acf":[],"blog_post_summary":"<p>Read what is new in F# 8 &#8211; the language, compiler tooling and FSharp.Core standard library<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/48815","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\/122898"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/comments?post=48815"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/48815\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media\/48925"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media?parent=48815"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/categories?post=48815"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/tags?post=48815"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}