What’s new in F# 6

Kathleen Dollard

We’re excited to announce the availability F# 6, shipping with .NET 6 RC2 and Visual Studio 2022 RC2. It’s the next step to making it easier for you to write robust, succinct and performant code. You can get F# 6 in the following ways:

F# 6 is about making F# simpler and more performant. This applies to the language design, library, and tooling. A major goal of long-term language evolution is to remove corner-cases in the language that surprise users or are unnecessary hurdles on the path to adoption. We are very pleased to have worked with the F# community in this release to make the F# language simpler, more performant, and easier to learn.

To learn more about F#, see The .NET Conf Focus Day on F# including the F# Bonanza, Guido van Rossum Learns F# and Starting Your F# Journey.

Making F# faster and more interopable with task {…}

One of the most requested features for F# – and the most significant technical feature in this release – has been to make authoring asynchronous tasks simpler, more performant and more interoperable with other .NET languages like C#. Previously, creating .NET tasks required using async {…} to create a task and then invoking it with Async.AwaitTask. With F# 6 you can now use task {…} directly to create the task and await it. For example, consider the following F# code to create a .NET-compatible task:

let readFilesTask (path1, path2) =
   async {
        let! bytes1 = File.ReadAllBytesAsync(path1) |> Async.AwaitTask
        let! bytes2 = File.ReadAllBytesAsync(path2) |> Async.AwaitTask
        return Array.append bytes1 bytes2
   } |> Async.StartAsTask

This code can now become:

let readFilesTask (path1, path2) =
   task {
        let! bytes1 = File.ReadAllBytesAsync(path1)
        let! bytes2 = File.ReadAllBytesAsync(path2)
        return Array.append bytes1 bytes2
   }

The built-in support for task {…} is available ubiquitously in F# code – no namespaces need to be opened.

Task support has previously been available for F# 5.0 through the excellent TaskBuilder.fs and Ply libraries. These guided the design of the support in F# 6 and provided an important source of tests. The authors of these libraries have been major contributors to the design of F# 6, both directly and indirectly. Migrating code to the built-in support should be straightforward. There are however some differences: namespaces and type-inference differ slightly between the built-in support and these libraries, and some additional type annotations may be needed. These community libraries can still be used with F# 6, if explicitly referenced and the correct namespaces opened in each file.

Using task {…} is very similar to async {…}, and both are supported. Using task {…} has several advantages over async {…}:

  • The performance of task {…} is much better.
  • Debugging stepping and stack traces for task {…} is better.
  • Interoperating with .NET packages expecting or producing tasks is easier.

If you’re familiar with async {…}, there are some differences to be aware of:

  • task {…} immediately executes the task to the first asynchronous yield.
  • task {…} does not implicitly propagate a cancellation token.
  • task {…} does not perform implicit cancellation checks.
  • task {…} does not support asynchronous tailcalls. This means using return! .. recursively may result in stack overflows if there are no intervening asynchronous yields.

In general, you should consider using task {…} over async {…} in new code if interoperating with .NET libraries that uses tasks. Review code before switching to task {…} to ensure you are not relying on the above characteristics of async {…}.

The task {…} support of F# 6 is built on a foundation called “resumable code” RFC FS-1087. Resumable code is a core technical feature which can be used to build many kinds of high-performance asynchronous and yielding state machines.

In the coming months we will be working with the F# community to utilise this feature to make available two key optional packages:

  1. A fast re-implementation of F# async {…} using resumable code.
  2. A fast re-implementation of asynchronous sequences asyncSeq {…} using resumable code.

Initially these will be delivered via community packages such as FSharp.Control.AsyncSeq. In future versions they may be integrated into FSharp.Core.

Making F# simpler to learn: indexing with expr[idx]

In F# 6, we begin allowing the syntax expr[idx] for indexing syntax.

Up to and including F# 5.0, F# has used expr.[idx] as indexing syntax. This syntax was based on a similar notation used in OCaml for string indexed lookup. Allowing the use of expr[idx] is based on repeated feedback from those learning F# or seeing F# for the first time that the use of dot-notation indexing comes across as an unnecessary divergence from standard industry practice. There is no need for F# to diverge here.

This is not a breaking change – by default no warnings are emitted on the use of expr.[idx]. However, some informational messages are emitted related to this change suggesting code clarifications, and some further informational messages can be optionally activated. For example, an optional informational warning (/warnon:3566) can be activated to start reporting uses of the expr.[idx] notation. See Indexer Notation for details.

In new code, we recommend the systematic use of expr[idx] as the indexing syntax.

Making F# faster: Struct representations for partial active patterns

F# includes the active patterns feature that allows users to extend pattern matching in intuitive and powerful ways. In F# 6 we’ve augmented that feature with optional Struct representations for active patterns. This allows you to use an attribute to constrain a partial active pattern to return a value option:

[<return: Struct>]
let (|Int|_|) str =
   match System.Int32.TryParse(str) with
   | true, int -> ValueSome(int)
   | _ -> ValueNone

The use of the attribute is required. At usage sites, code doesn’t change. The net result is that allocations are reduced.

Making F# more uniform: overloaded custom operations in computation expressions

F# 6 activates the feature “overloaded custom operations in computation expressions” which has been in preview since F# 5.0. This allows for simpler DSLs in F# including for validation and web programming.

The feature was previously described in the announcement for F# 5.0 preview and implements F# RFC 1056.

Making F# more uniform: “as” patterns

In F# 6, the right hand side of an “as” pattern can now itself be a pattern. This is important when a type test has given a stronger type to an input. For example, consider the code

type Pair = Pair of int * int

let analyzeObject (input: obj) =
    match input with
    | :? (int * int) as (x, y) -> printfn $"A tuple: {x}, {y}"
    | :? Pair as Pair (x, y) -> printfn $"A DU: {x}, {y}"
    | _ -> printfn "Nope"

let input = box (1, 2)

In each pattern case the input object is type-tested. The right-hand-side of the “as” pattern is now allowed to be a further pattern which can itself match the object at the stronger type.

Making F# more uniform: Indentation syntax revisions

The F# community has contributed some key improvements to make the F# language more uniform in F# 6. The most important of these is removing a number of inconsistencies and limitations in F#’s use of indentation-aware syntax, see RFC FS-1108. This resolved 10 significant issues highlighted by the F# users since F# 4.0.

For example, in F# 5.0 the following was allowed:

let c = (
    printfn "aaaa"
    printfn "bbbb"
)

However the following was not (producing a warning)

let c = [
    1
    2
]

In F# 6, both are allowed. This makes F# simpler and easier to learn. The F# community contributor Hadrian Tang has led the way on this including remarkable and highly valuable systematic testing of the feature.

Making F# more uniform: Discards on use bindings

F# 6 allows _ to be used in a use binding, for example:

let doSomething () =
    use _ = System.IO.File.OpenText("input.txt")
    printfn "reading the file"

This feature implements F# RFC FS-1102.

Making F# more uniform: Formatting for binary numbers

F# 6 adds the %B pattern to the available format specifiers for binary number formats. Consider the following F# code:

printf "%o" 123
printf "%B" 123

This code prints the following output:

173
1111011

This feature implements F# RFC FS-1100.

Making F# faster: InlineIfLambda

In F# 6, we’ve added a new declarative feature that allows code to optionally indicate that lambda arguments should be inlined at callsites.

For example, consider the following iterate function to traverse an array:

let inline iterateTwice ([<InlineIfLambda>] action) (array: 'T[]) = 
    for j = 0 to array.Length-1 do 
        action array[j]
    for j = 0 to array.Length-1 do 
        action array[j]

If the callsite is

let arr = [| 1.. 100 |]
let mutable sum = 0
arr  |> iterateTwice (fun x -> 
    sum <- sum + x) 

then after inlining and other optimizations the code becomes:

let arr = [| 1.. 100 |]
let mutable sum = 0
for j = 0 to array.Length-1 do 
    sum <- array[i] + x
for j = 0 to array.Length-1 do 
    sum <- array[i] + x

Unlike previous versions of F#, this optimization applies regardless of the size of the lambda expression involved. This feature can also be used to implement loop unrolling and similar transformations reliably.

An opt-in warning (/warnon:3517, off by default) can be turned on to indicate places in your code where InlineIfLambda arguments are not bound to lambda expressions at callsites. In normal situations, this warning should not be enabled, however in certain kinds of high-performance programming it can be useful to ensure all code is inlined and flattened.

Making F# faster: Improved performance and debugging for list and array expressions

In F# 6, the compiled form of generative list and array expressions is now up to 4x faster (see the relevant pull request). Generative list and array expressions also have greatly improved debugging. For example:

let generateList (i: int) =
    [ "a"
      "b"
      for j in 1 .. 10 do
          "c"
          if i % 3 = 0 then 
              "d"
      "e"
    ]

This function generates lists of size 13 or 23 depending on the input and now executes more efficiently and breakpoints can be set on the individual lines

Making F# simpler and more interoperable: Implicit conversions

In F# 6 we have activated support for additional “implicit” and “type-directed” conversions in F#, as described in RFC FS-1093.

This change achieves three things.

  1. Fewer explicit upcasts are required
  2. Fewer explicit integer conversions are required
  3. First-class support for .NET-style implicit conversions is added

Additional implicit upcast conversions

In F# 5.0 and before, upcasts were needed for the return expression when implementing a function where the expressions have different subtypes on different branches, even when a type annotation was present. Consider the following F# 5.0 code (StreamReader derives from TextReader):

open System
open System.IO

let findInputSource() : TextReader = 
    if DateTime.Now.DayOfWeek = DayOfWeek.Monday then  
        // On Monday a TextReader
        Console.In
    else
        // On other days a StreamReader
        File.OpenText("path.txt") :> TextReader

Here the branches of the conditional compute a TextReader and StreamReader respectively, and the upcast was added to make both branches have type TextReader. In F# 6, these upcasts are now added automatically. This means the code can now be simpler:

let findInputSource() : TextReader = 
    if DateTime.Now.DayOfWeek = DayOfWeek.Monday then  
        // On Monday a TextReader
        Console.In
    else
        // On other days a StreamReader
        File.OpenText("path.txt")

You may optionally enable the warning /warnon:3388 to show a warning at every point an additional implicit upcast is used, as described below.

Implicit integer conversions

Type-directed conversions also allow for automatically widening 32-bit integers to 64-bit integers more often. For example, the use of 64-bit integers is now ubiquitous in machine-learning libraries. Consider a typical API shape:

type Tensor(…) =
    static member Create(sizes: seq<int64>) = Tensor(…)

In F# 5.0, integer literals for int64 must be used:

Tensor.Create([100L; 10L; 10L])

or

Tensor.Create([int64 100; int64 10; int64 10])

In F# 6, widening happens automatically for int32 to int64, int32 to nativeint and int32 to double, when both source and destination type are known during type inference, so in cases such as the above int32 literals can be used:

Tensor.Create([100; 10; 10])

Despite this change, F# still continues to use explicit widening of numeric types in most cases. For example, implicit widening does not apply to other numeric types such as int8 or int16, nor from float32 to float64, nor when either source or destination type is unknown. You may also optionally enable the warning /warnon:3389to show a warning at every point op_Implicit is used for method arguments.

NOTE: High-performance modern tensor implementations are available through TorchSharp and TensorFlow.NET.

First-class support for .NET-style implicit conversions

The addition of type-directed conversions allows .NET “op_Implicit” conversions to be applied automatically in F# code when calling methods. For example, in F# 5.0 it was necessary to use XName.op_Implicit when working with .NET APIs for XML:

open System.Xml.Linq

let purchaseOrder = XElement.Load("PurchaseOrder.xml")

let partNos = purchaseOrder.Descendants(XName.op_Implicit "Item")

In F# 6, op_Implicit conversions are applied automatically for argument expressions when they are available and the types are known for the source expression and target type:

open System.Xml.Linq

let purchaseOrder = XElement.Load("PurchaseOrder.xml")

let partNos = purchaseOrder.Descendants("Item")

You may optionally enable the warning /warnon:3390 to show a warning at every point op_implicit numeric widening is used, as described below.

Optional warnings for implicit conversions

When used widely, or inappropriately, type-directed and implicit conversions can interact poorly with type inference and lead to code that is harder to understand. For this reason, some mitigations are in-place to help ensure this feature is not widely abused in F# code. First, both source and destination type must be strongly known, with no ambiguity or additional type inference arising. Secondly, opt-in warnings can be activated to report any use of implicit conversions, with one warning on by default:

  • /warnon:3388 (additional implicit upcast)
  • /warnon:3389 (implicit numeric widening)
  • /warnon:3390 (op_Implicit at method arguments)
  • /warnon:3391 (op_Implicit at non-method arguments, on by default)

If your team wants to ban all uses of implicit conversions you can also use /warnaserror:3388, /warnaserror:3389, /warnaserror:3390, /warnaserror:3391.

Making F# simpler: Updating immutable collections

This release sees the addition of five new operations to the core collection functions in FSharp.Core. These are:

  • List/Array/Seq.insertAt
  • List/Array/Seq.removeAt
  • List/Array/Seq.updateAt
  • List/Array/Seq.insertManyAt
  • List/Array/Seq.removeManyAt

These all perform copy-and-update operations on the corresponding collection type or sequence. Examples of using these functions can be seen in the documentation, e.g. for List.insertAt.

As an example, consider the model, message and update logic for a simple “Todo List” application written in the Elmish style. Here the user interacts with the application, generating messages, and the update function processes these messages, producing a new model:

type Model =
    { ToDo: string list }

type Message =
    | InsertToDo of index: int * what: string
    | RemoveToDo of index: int
    | LoadedToDos of index: int * what: string list

let update (model: Model) (message: Message) =
    match message with
    | InsertToDo (index, what) -> 
        { model with ToDo = model.ToDo |> List.insertAt index what }
    | RemoveToDo index -> 
        { model with ToDo = model.ToDo |> List.removeAt index }
    | LoadedToDos (index, what) -> 
        { model with ToDo = model.ToDo |> List.insertManyAt index what }

With these new functions, the logic is clear and simple and relies only on immutable data.

Making F# simpler: Map has Keys and Values

In FSharp.Core 6.0.0.0 the Map type new supports Keys and Values properties. These do not copy the underlying collection.

Making F# simpler: Additional intrinsics for NativePtr

In FSharp.Core 6.0.0.0 we add new intrinsics to the NativePtr module:

  • NativePtr.nullPtr
  • NativePtr.isNullPtr
  • NativePtr.initBlock
  • NativePtr.clear
  • NativePtr.copy
  • NativePtr.copyBlock
  • NativePtr.ofILSigPtr
  • NativePtr.toILSigPtr

As with other functions in NativePtr these functions are inlined and their use emits warnings unless /nowarn:9 is used. The use of these functions is restricted to unmanaged types.

Making F# more uniform: Additional numeric types with unit annotations

F# supports Units of Measure, allowing annotation tags to be added to numeric types. However, in previous versions not all numeric types supported these annotations. In F# 6, the following types or type abbreviation aliases now support unit-of-measure annotations, the new additions are shown with a +:

F# alias CLR Type
float32/single++ System.Single
float/double+ System.Double
decimal System.Decimal
sbyte/int8+ System.SByte
int16 System.Int16
int/int32+ System.Int32
int64 System.Int64
byte+/uint8+ System.Byte
uint16+ System.UInt16
uint+/uint32+ System.UInt32
uint64+ System.UIn64
nativeint+ System.IntPtr
unativeint+ System.UIntPtr

For example, you can annotate an unsigned integer as follows:

[<Measure>] 
type days

let better_age = 3u<days>

This design revision was championed, designed and implemented by community member Paulmichael Blasucci.

Bringing F# forward: Reducing use of rarely used symbolic operators

In F# programming, “ref cells” can be used for heap-allocated mutable registers. While they are occasionally useful, in modern F# coding these are rarely needed as let mutable can normally be used instead. The F# core library includes two operators := and ! and two functions incr and decr specifically related to reference calls. The presence of these operators makes reference cells more central to F# programming than they need to be, requiring all F# programmers to know these operators. Further, the ! operator can be easily confused with the not operation in C# and other languages, a potentially subtle source of bugs when translating code.

As a result, in F# 6 we’ve made a decision to give soft guidance that de-normalizes the use of :=, !, incr and decr in F# 6 and beyond. Using these operators and functions will now given informational messages asking you to replace your code with explicit use of the Value property.

The rationale for this change is to reduce the number of operators the F# programmer needs to know.

For example, consider the following F# 5.0 code:

let r = ref 0

let doSomething() =
    printfn "doing something"
    r := !r + 1

First, in modern F# coding reference cells are rarely needed, as let mutable can normally be used instead:

let mutable r = 0

let doSomething() =
    printfn "doing something"
    r <- r + 1

If reference cells are used, then the in F# 6 an informational warning is emitted asking you to change the last line to r.Value <- r.Value + 1, and linking you to further guidance on the appropriate use of reference cells.

let r = ref 0

let doSomething() =
    printfn "doing something”"
    r.Value <- r.Value + 1

These messages are not warnings – they are “informational messages” shown in the IDE. This remains backwards-compatible, and is described in RFC FS-1111.

Bringing F# forward: Removing long-deprecated legacy features

F# 2.0 deprecated several F# features, giving warnings if they were used. These features been removed in F# 6.0 and will give errors, unless you explicitly use /langversion:5.0 or before. The features that now give errors are:

  • Multiple generic parameters using a postfix type name, for example (int, int) Dictionary. This becomes an error in F# 6 and the standard Dictionary<int,int> should be used instead.
  • #indent "off". This becomes an error in F# 6
  • x.(expr). This becomes an error in F# 6
  • module M = struct … end . This becomes an error in F# 6
  • Use of inputs *.ml and *.mli. This becomes an error in F# 6
  • Use of (*IF-CAML*) or (*IF-OCAML*). This becomes an error in F# 6
  • Use of land, lor, lxor, lsl, lsr or asr as infix operators. These are infix keywords in F# because they were infix keywords in OCaml and are not defined in FSharp.Core. Using these keywords will now emit a warning (not error)

These are described in RFC FS-1114

F# tooling: Pipeline debugging!

One of the most anticipated features in the F# 6 toolchain is the addition of “Pipeline debugging”. Now, the F# compiler emits debug stepping points for each position in an F# pipeline involving |>, ||> and |||> operators. At each step, the input or intermediate stage of the pipeline can be inspected in a typical debugger.

For example:

Stepping in pipeline debugging, What’s new in F# 6

Breakpoints can also be set at each point in the pipeline, for example:

Breakpoints in pipeline debugging

Pipeline debugging activates by default when you re-compile your code with the F# 6 compiler, regardless of the language version or IDE you are using. Play with stepping and breakpoints and let us know what you think!

F# tooling: Value shadowing shown in debugger

The updated F# 6 toolchain makes another important improvement to debugging. For example, consider the code

let someFunctionUsingShadowing x =
    let yyy = "aa"
    let yyy = "aa".Length - 1
    yyy + 3 

Here the second yyy “shadows” the first. This is allowed in F# and the technique is a common one because F# defaults to immutable bindings, hence new versions of bindings often replace previous ones.

When using an F# 6 toolchain, the names associated with locals are adjusted for those portions of scopes where shadowing occurs. For example, when a breakpoint is placed on the expression yyy + 3 of the above function, the locals are displayed as follows:

Value shadowing in debugger, What’s new in F# 6

F# tooling: Performance and Scalability

In F# 6, we have made dramatic improvements to Compiler/IDE-tooling perf/scalability improvements in both the core implementation of the language and the Visual Studio components.

  • The F# compiler now performs the parsing stage in parallel, resulting in approximately 5% performance improvement for large projects.
  • Analysis results are now performed concurrently. When working in the IDE, analysis requests are no longer serialized through a single “reactor” compilation thread. Instead, the F# Compiler Service is now concurrent. This greatly improves performance in many situations.
  • Improved performance of analysis in F# projects that contain signature files. If you are using signature files in your project you will see diagnostics and other analysis results much more quickly when a change is made to an implementation file without change to the signature file. This change was also included in the final releases of Visual Studio 2019.
  • Find-all references is now performed concurrently, in parallel across multiple projects.
  • Major performance improvements to “Close solution” and “Switch configuration”. For example, some simple use cases have reduced “Close solution” from 16 seconds to 1 second. For a demonstration of this see this tweet by contributor Will Smith.

F# tooling: In-memory cross-project referencing!

In F# 6, we made working between F# and C# projects simpler and more reliable through “In-memory cross-project referencing” from F# to C#.

This means in C# projects are now reflected immediately in a F# project without having to compile the C# project on-disk. It also means analysis results are available in uncompiled solutions.

F# tooling: .NET Core the default for scripting

If you open or execute an F# Script (.fsx) in Visual Studio, by default the script will be analysed and executed using .NET Core with 64-bit execution. This functionality was in preview in Visual Studio 2019 and is now enabled by default.

To enable .NET Framework scripting, use Tools -> Options -> F# Tools -> F# Interactive and set Use .NET Core Scripting to false and restart the F# Interactive window. This setting affects both script editing and script execution.

64-bit scripting is now also the default. To enable 32-bit execution for .NET Framework scripting, also set 64-bit F# Interactive to false. There is no 32-bit option for .NET Core scripting.

F# tooling: F# scripts now respect global.json

If you execute a script using dotnet fsi in a directory containing a global.json with a .NET SDK setting, then the listed version of the .NET SDK will be used to execute and for editing the the script. This feature has been in preview in the later versions of .NET 5.

For example, assume is a script is in a directory with the following global.json specifying a .NET SDK version policy:

{
  "sdk": {
    "version": "5.0.200",
    "rollForward": "minor"
  }
}

This is a powerful feature that lets you “lock down” the SDK used to compile, analyse and execute your scripts.

  • If you now execute the script using dotnet fsi, from this directory, the SDK version will be respected. If the SDK is not found, you may need to install it on your development machine.
  • Visual Studio and other IDEs will respect this setting when you are editing script.
  • When you start or reset the F# Interactive evaluation window in Visual Studio, the SDK choice is delayed until you first use “Send to Interactive” (Alt-Enter) from the script. At this point, the SDK chosen will the one used for editing and executing the script.

On Linux and other UNIX systems you can combine a global.json with a shebang with a language version for direct execution of the script. A simple shebang for script.fsx is:

#!/usr/bin/env -S dotnet fsi

printfn "Hello, world"

The script can be executed directly with script.fsx. You can combine this with a specific, non-default language version like this:

#!/usr/bin/env -S dotnet fsi --langversion:5.0

Note that this setting will be ignored by editing tools, which will analyse the script assuming latest language version.

F# tooling: .NET Interactive in Visual Studio Code and Visual Studio!

.NET Interactive Notebooks allow you to use notebook-style programming with F# and C#. Many improvements have been made in recent releases including excellent support in Visual Studio Code.

Also just announced is a Notebook Editor extension for Visual Studio 2022.

Making F# simpler to learn: Code Examples!

In October, the F# community has worked on an initiative to add code examples for all public entry points to the F# Core Library. 800 code examples have now been contributed! For example:

  • All functions in the List, Array, Seq and Map modules now have code examples.
  • Functions and methods in advanced modules such as Quotations now have examples.

You can contribute to this initiative via this GitHub issue.

System.Text.Json support for common F# types

Starting with .NET 6 System.Text.Json will have built-in support for common F# types. An example is shown below.

Image json from f#

User-defined discriminated unions are not yet supported in this way. Thank you to Eirik Tsarpalis for contributing this to .NET.

General Improvements in .NET 6

F# 6 is built on .NET 6, and F# programmers benefit both directly and indirectly from a host of new features and improvements in the runtime. Some of these are listed below, summarized from the announcements for .NET 6 Release Candidate 1 and previous release announcements.

  • Source build, a scenario and infrastructure being developed with Red Hat that is intended to satisfy packaging rules of commonly used Linux distributions, such as Debian and Fedora. Also, source build is intended to enable .NET Core contributors to build a .NET Core SDK with coordinated changes in multiple repositories. The technology is intended to solve common challenges that developers encounter when trying to build the whole .NET Core SDK from source.
  • Profile-guided optimization, which is based on the assumption that code executed as part of a startup often is uniform and that higher level performance can be delivered by leveraging it. PGO can compile startup code at higher quality, reduce binary size, and rearrange application binaries so code used at startup is co-located near the start of the file.
  • Dynamic PGO, a mirror image of PGO, integrated with the RyuJIT .NET just-in-time compiler. Performance is improved.
  • Crossgen2, to generate and optimize code via ahead-of-time compilation, is now enabled by default when publishing ReadyToRun images.
  • Intel Control Enforcement Technology (CET), available in some new Intel and AMD processors to protect against common types of attacks involving control-flow hijacking.
  • HTTP/3, previewed in .NET 6, solves functional and performance challenges with previous versions of HTTP.
  • W^X, a security mitigation to block attack paths by disallowing memory pages to be writable and executable at the same time.

Other improvements in .NET 6 include:

  • Interface casting performance has been improved by 16 percent to 38 percent.
  • Code generation has been improved in RyuJIT via multiple changes, to make the process more efficient or resulting code run faster.
  • Single-file bundles now support compression.
  • Single-file application publishing improvements including improved analysis to allow for custom warnings.
  • Enhanced date, time, and time zone support.
  • Significantly improved FileStream performance on Windows.
  • OpenSSL 3 support has been added for cryptography on Linux.
  • OpenTelemetry Metrics API support has been added. OpenTelemetry, which has been supported in recent .NET versions, promotes observability.
  • WebSocket compression for libraries reduces the amount of data transmitted over a network.
  • Package validation tools enable NuGet library developers to validate that packages are consistent and well-formed.
  • TLS support for DirectoryServices.Protocols.
  • Native memory allocation APIs.

General Improvements in Visual Studio

The release of F# 6 coincides with the release of Visual Studio 2022 on Windows. F# programmers using this IDE experience will benefit from many improvements in this release:

  • Visual Studio 2022 on Windows is now a 64-bit application. This means you can open, edit, run, and debug even the biggest and most complex solutions without running out of memory.
  • Find in Files is faster. Find in Files is now as much as 3x faster when searching large solutions such as Orchard Core.
  • Multi-repo support with Git in the IDE. If you’ve worked with projects hosted on different Git repositories, you might have used external tools or multiple instances of Visual Studio to connect to them. With Visual Studio 2022, you can work with a single solution that has projects in multiple repositories and contribute to them all from a single instance of Visual Studio.
  • Personalization improvements. For example, Visual Studio 2022 offers you the ability to sync with your Windows theme – if you’ve enabled the “night light” feature there, Visual Studio uses it, too.

What’s next

Now that F# 6 is released, we’re moving our focus to a few areas:

  1. Simplifying the F# learning experience
  2. Continuing to modernize the F# language service implementation
  3. Planning for the next F# version

Cheers, and happy F# coding!

Thanks and Acknowledgments!

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’re proud to be a contributing part of this community.

Many people have contributed directly and indirectly to F# 6, including all who have contributed to the F# Language Design Process through fsharp/fslang-suggestions and fsharp/fslang-design repositories. Robert Peele developed TaskBuilder.fs and Nino Floris developed Ply.

Will Smith, Kevin Ransom, Vlad Zarytovskii, Phillip Carter, Don Syme, Brett V. Forsgren contributed directly at Microsoft under the guidance of Jon Sequeira and Tim Heuer.

Eugene Auduchinok made 41 PRs to the F# core implementation, including significant improvements to error recovery. Florian Verdonck made 24 PRs, including improved source trees for the Fantomas source code formatter. Hadrian Tang made 22 PRs, including several of the language features and simplifications outlined in this blog post. GitHub user kerams made 13 pull requests, including support for SkipLocalsInit and performance improvements. Scott Hutchinson made 9 PRs, Goswin Rothenthal made 7 PRs, Ryan Coy made 6 PRs, Victor Baybekov made 5 PRs, including performance improvements to Map and Set in FSharp.Core. Some of these improvements were shipped in later versions of FSharp.Core 5.0.x.

Other direct contributors to the dotnet/fsharp repository in the F# 6.0 time period include forki, smoothdeveloper, gsomix, cristianosuzuki77, ErikSchierboom, jonfortescue, teo-tsirpanis, MaxWilson, mmitche, DedSec256, uxsoft, Swoorup, En3Tho, 0x6a62, uweigand, MecuStefan, JYCabello, Yatao Li, amieres, chillitom, Daniel-Svensson, Krzysztof-Cieslak, thinkbeforecoding, abelbraaksma and ShalokShalom.

Many other people are contributing to the rollout of F# 6 and .NET 6 in Fable, Ionide, Bolero, FSharp.Data, Giraffe, Saturn, SAFE Stack, WebSharper, FsCheck, DiffSharp, Fantomas and other community-delivered technologies.

Contributor Showcase

In this and future announcements we will highlight some of the individuals who contribute to F#. This text is written in the contributors’ own words:

I’m Nino Floris, co-founder of Crowded and contributor to Npgsql and other projects. At Crowded we endeavor to create a friendly community platform, written in F# with a focus on fast apis and principled flexibility. I am interested in statically typed programming languages that bring adaptable, performant, and usable programming experiences to more places. Small open source communities with big impact hold a special place in my heart – when I’m not busy with work you may see me contributing to Npgsql, (web) frameworks, or sharing my enthusiasm for programming by helping people in the community.

Contributor photo

I’m Hadrian Tang, a 19-year-old student at Hong Kong University of Science and Technology studying Information Systems for the second year. I started programming in 2014 by picking up a book on VB.NET at the school library. I moved to C#, then started looking into F# after reading a comment by Charles Roddie in 2018. I was reluctant to adopt F#, but the simple syntax and interoperability ultimately won me over. However, like every language, F# has much to improve, which is why I’m the author of 17% of currently open issues at the F# suggestions repo. Last summer, I took a shot at some of the easy features that ultimately made it to F# 6. I use F# for mobile development using and for my next project plan to use Fable and web interfaces to improve interoperability with JavaScript’s huge array of libraries, teaming up with [WhiteBlackGoose] (https://github.com/WhiteBlackGoose).

Much thanks to Don Syme for creating such an amazing language with simple syntax and great interoperability, and to the F# Discord for a great place to help each other: I could get help from the community with any problems that I faced. Moreover, GitHub and .NET also give a great foundation for all F# work. I love how F# is agile: starting PRs to implement unapproved features can lead to approval. I look forward to the future of F#, especially with the theme of making F# simple! And much thanks to the F# community as a whole for libraries, tooling and support!

Hadrian Tang photo

16 comments

Discussion is closed. Login to edit/delete existing comments.

  • Ayman Elfaki 0

    Thank you for the awesome work.

    What are the plans for code formating and code lens support in Visual Studio?

    • Kathleen DollardMicrosoft employee 0

      Can you clarify what you mean by CodeLens? The C#/VB style member headers or the preview F# feature that shows signatures at the end of the line (in preview and rather un-pretty sometimes). Also, what kind of code formatting do you want to see – there’s a pretty broad range.

  • Tony Henrique 0

    Very cool! Great new features and good to see going in this direction of be more friendly for newcomers.

  • Alex Corrado 0

    Install the latest .NET 6 SDK RC2 preview

    The download at that link says it has F# 5.0

    EDIT: Despite what it says on that page, it does indeed seem to install F# 6.0

    • Kathleen DollardMicrosoft employee 0

      Yes, this is an error we are in the process of fixing.

  • Alfonso García-Caro 0

    This is an excellent article! I didn’t know there were so many things coming in F# 6, I’m now very excited by the new release. Thanks a lot for the write up!

  • Asti R 0

    A truly great release! Can’t wait to upgrade.

    The last line in implicit conversions:

    You may optionally enable the warning /warnon:3390 to show a warning at every point implicit numeric widening is used, as described below.

    should read

    You may optionally enable the warning /warnon:3390 to show a warning at every point op_Implicit is used for method arguments.

  • Mark Pattison 0

    Markdown typos in this and the second contributor showcase.

  • Nathaniel Elkins 0

    Amazing, looking forward to playing with this.

    I notice the old indexing syntax was used for the InlineIfLambda example. Is this intentional? I suppose it doesn’t matter too much either way.

  • Krzysztof Skowronek 0

    Functions for collections and pipeline step debugging are THE BEST!

    What about hot reload? Does it work with F#?

    And yes, I’m really sad that dotnet watch is longer for us 🙁

    • Kathleen DollardMicrosoft employee 0

      F# does not support hot reload, because hot reload is based on Edit and Continue, which F# does not support. 🙁

  • Kouji Matsui 0

    Awsome improvements!

Feedback usabilla icon