Announcing F# 5

Phillip Carter

Phillip

Today, we’re excited to announce the general availability of F# 5. It ships with .NET 5. We’ve been working on F# 5 for over the past year now, and we’re excited to share it with everyone.

You can get F# 5 in the following ways:

If you’re using Visual Studio on Windows, you’ll need to upgrade to the latest 16.8 release.

What F# 5 is all about

From F# 4.1 to F# 5, the chief focus for F# has been bringing up great support for .NET Core (now .NET 5). With F# 5, we’re considering this journey mostly complete. F# 5 marks the start of a new era of F# evolution centered around three main things:

  1. Interactive programming
  2. Making analytical-oriented programming convenient and fun
  3. Great fundamentals and performance for functional programming on .NET

We started F# 5 with roughly these same goals, stating in the first preview that “F# 5 is focused on better interactive and analytical programming”. This remains true, and we also added a few more orthogonal features that everyone can enjoy regardless of how they’re using F#.

Using F# 5

F# 5 is the new default language version for the .NET SDK and Visual Studio. Any new or existing project compiled with either of those toolsets will use F# 5.

You can also use F# 5 in the .NET and Jupyter Notebooks support.

Check out the sample repository that shows off some of what you can do with F# 5. You can play with each of the features there instead of starting from scratch.

FSharp.Core now targets netstandard2.0 only

Starting with F# 5, FSharp.Core 5.0.0 and higher now only targets .NET Standard 2.0. We have dropped out .NET Framework 4.5 target and its dependencies to simplify the package and continue to modernize F#.

If you are using F# on .NET Framework 4.7 or older, you should retarget to .NET Framework 4.7.2 or higher. If you cannot do so, then you should explicitly pin your FSharp.Core reference(s) to an older version, and it is also likely that F# 5 is not the best choice for you right now.

If you were targeting .NET Core or .NET Standard already, nothing is different, since you have already been using the .NET Standard 2.0 build of FSharp.Core this entire time.

Package references in F# scripts

F# 5 brings support for package references in F# scripts with #r "nuget:..." syntax. Here’s how it looks for most packages:

Package references support packages with native dependencies, such as ML.NET.

Package references also support packages with special requirements about referencing dependent .dlls. For example, the FParsec package used to require that users manually ensure that its dependent FParsecCS.dll was referenced first before FParsec.dll was referenced in F# Interactive. This is no longer needed, and you can simply just reference the package like this:

Package references are the basis for acquiring any package when using F# in Jupyter Notebooks or VSCode Notebooks.

Special thanks to Steffen Forkmann for supplying a prototype of this feature several years ago, of which much of the final implementation is still based on. Another special thanks to Gauthier Segay for the initial implementation of an extensibility mechanism for the feature, which can be used to “plug in” other dependency managers in the future.

Support for Jupyter, nteract, and VSCode Notebooks

To coincide with package references, F# 5 is fully supported in Jupyter Notebooks, nteract, and VSCode Notebooks.

Here’s an example of what the VSCode Notebooks support looks like:

F# package reference and code in a VSCode notebook cell

It also supports inline charting:

F# plotting code generating a chart in VSCode notebooks

And allows you to import and export Jupyter Notebooks:

Menu showing import and export for jupyter notebooks

VSCode Notebooks themselves are still in preview, but they already support quite a few features:

  • Preliminary language service support
  • Inline charting and formatting of data
  • Compact data format (.dib) that makes code review easy
  • Ability to import Jupyter notebooks (.ipynb) and convert to a .dib
  • Ability to export a .dib notebook as a Jupyter notebook (.ipynb)
  • Sharing F#-defined values with JavaScript cells
  • Sharing F#-defined values with C# cells

There are more features on the roadmap:

  • Better IntelliSense
  • Sharing more complex data between languages

We’d love to have you try it out and give us feedback on what you feel needs to be there. To do so, follow the installation instructions and don’t be shy when filing issues on GitHub!

String Interpolation

String Interpolation is one of the most highly-requested language features and the very first feature that we had an initial design for in the F# Language Design repository. The design has undergone a lot of discussion over the years, but finally a breakthrough on how to best handle it was made by Yatao Li, who also supplied an initial implementation.

F# interpolated strings are fairly similar to C# or JavaScript interpolated strings, in that they let you write code in “holes” inside of a string literal. Here’s a basic example:

However, F# interpolated strings also allow for typed interpolations, just like the sprintf function, to enforce that an expression inside of an interpolated context conforms to a particular type. It uses the same format specifiers.

For more advanced usage, you can write multiple expressions inside of an interpolation (and technically almost an entire program). That said, it’s usually better to keep function definitions outside of interpolated strings as much as possible!

Support for nameof

Another highly-requested feature of F# 5 is nameof which resolves the symbol it’s being used for and produces its name in F# source. This is useful in various scenarios, such as logging, and protects your logging against changes in source code.

The last line will throw an exception and “month” will be shown in the error message.

You can take a name of nearly everything in F#:

Three final additions are changes to how operators work: the addition of the nameof<'type-parameter> form for generic type parameters, and the ability to use nameof as a pattern in a pattern match expression.

The nameof<'type-parameter> form aligns with how typeof and typedefof work in F# today.

Open Type declarations

F# 5 also adds support for open type declarations. An open type declaration is like opening a static class in C#, except with some different syntax and some slightly different behavior to fit F# semantics.

With Open Type Declarations, you can open any type to expose static contents inside of it. Additionally, you can open F#-defined unions and records to expose their contents. For example, this can be useful if you have a union defined in a module and want to access its cases, but don’t want to open the entire module.

Enhanced Slicing

Slicing data types is critical when doing analytical work on sets of data. To that end, we enhanced F# slicing in two areas for release, with one still considered preview.

Consistent behavior for built-in data types

Behavior for slicing the built-in FSharp.Core data types (array, list, string, 2D array, 3D array, 4D array) used to not be consistent prior to F# 5. Some edge-case behavior threw an exception and some wouldn’t. In F# 5, all built-in types now return empty slices for slices that are impossible to generate:

Fixed-index slices for 3D and 4D arrays in FSharp.Core

The built-in 3D and 4D array types have always supported slices, but they did not support fixing a particular index (such as the y-dimension in a 3D array). Now they do!

To illustrate this, consider the following 3D array:

z = 0

xy01
001
123

z = 1

xy01
045
167

What if you wanted to extract the slice [| 4; 5 |] from the array? This is now very simple!

This kind of slice used to not be possible prior to F# 5.

F# quotations improvements

We’re finishing up a fundamental improvement to F# Code Quotations, a metaprogramming feature that lets you generate and manipulate an abstract syntax tree that represents the F# code.

Although powerful, F# Code Quotations have had a severe deficiency up until this point: they didn’t carry “trait calls” to sufficiently represent the actual semantics of the code being “quoted” if it relied on type constraints. A common way this could manifest itself was “allowing” code with arithmetic that would merely throw an exception at runtime.

These enhancements are particularly relevant to translating F# code to run on other runtimes like PyTorch or ONNX, a key scenario we’re exploring as a means to attract developers in more “analytical” domains to F# and .NET. We also anticipate numerous smaller issues that F# developers using F# Code Quotations today had to work around to be resolved, with the code they expect to “just work” actually live up to that phrase.

A trivial example of code that now just works is as follows:

This used to throw an exception. It now emits -1 as you would expect it to.

Applicative Computation Expressions

Computation expressions (CEs) are used today to model “contextual computations”, or in more functional programming friendly terminology, monadic computations. However, they are a more flexible construct than just offering syntax for monads (you can encode monoids or even a computation expression that explicitly violates every monad law if you like).

F# 5 introduces applicative CEs, which are a slightly different form of CE than what you’re perhaps used to. Applicative CEs allow for significantly more efficient computations provided that every computation is independent, and their results are merely accumulated at the end. When computations are independent of one another, they are also trivially parallelizable. This benefit comes at a restriction, though: computations that depend on previously-computed values are not allowed.

The follow example shows a basic applicative CE for the Result type.

Work for Applicative CEs was done in collaboration with G-Research, who frequently contribute to the F# ecosystem. Thanks, folks!

If you’re a library author who exposes CEs in their library today, there are some additional considerations you’ll need to be aware of. These will be documented in the Computation Expressions article by release.

For consumers of applicative CEs, things aren’t too different from the CEs that you already use. The previously-mentioned restriction around independent computations is the key concept to understand.

Improved stack traces in F# async and other computation expressions

Thanks to a contribution by Nino Floris, stack traces coming from caught exceptions in computation expressions (such as F# async) now retain more information. Consider the following code that uses the Ply library:

Prior to F# 5, the origin function would not appear in stack traces without a workaround in the Ply library (and any other library where this is a scenario). Now it shows the full trace:

Improved .NET interop

F# 5 features several improvements to .NET interop.

Interfaces can be implemented at different generic instantiations

You can now implement the same interface at different generic instantiations. Lukas Rieger contributed an initial design and implementation of this feature.

Default interface member consumption

F# 5 lets you consume interfaces with default implementations.

Consider an interface defined in C# like this:

You can consume it in F# through any of the standard means of implementing an interface:

This lets you safely take advantage of C# code and .NET components written in modern C# when they expect users to be able to consume a default implementation.

Better interop with nullable value types

Nullable (value) types (called Nullable Types historically) have long been supported by F#, but interacting with them has traditionally been somewhat of a pain since you’d have to construct a Nullable or Nullable<SomeType> wrapper every time you wanted to pass a value. Now the compiler will implicitly convert a value type into a Nullable<ThatValueType> if the target type matches. The following code is now possible:

Improved runtime performance

.NET 5 brings improvements to the performance of processing .tail calls, which F# emits in various scenarios (especially in recursive and async code). The impact of this on the runtime performance of your app can vary depending on the kind of code you’re writing, as not all .tail calls are equal. However, you should generally expect an improvement to runtime performance compared to .NET Core 3.1.

Improved Map and Set performance in FSharp.Core

The runtime performance of the Map and Set data structure has improved considerably thanks to work by Victor Baybekov.

For Map, the improved operations are:

  • Retrieval via index – up to 50% faster
  • containsKey – up to 50% faster
  • count – up to 50% faster
  • iter – up to 15% faster
  • add – up to 40% faster
  • remove – up to 33% faster

For Set, the improved operations are:

  • containsKey – up to 40% faster
  • isSubsetOf – up to 40% faster
  • max – up to 50% faster
  • count – up to 50% faster
  • add – up to 30% faster
  • remove – up to 15% faster

Memory usage for each operation is also slightly improved. Thanks, Viktor!

Improved compiler performance

F# 5 brings along some performance improvements for the compiler and editor tooling.

F# compiler performance has steadily improved over the years. I’ll demostrate this by compiling the core project in the FSharpPlus library. This core project makes use of a lot of F# constructs in a way that acts as a great stress test for the compiler itself. I ran the following commands against F# 5, F# 4.7, and F# 4.5.

dotnet clean
dotnet msbuild /m:1 /clp:PerformanceSummary

These commands clean the output directories and force msbuild to run serially (though it would likely be serial anyways since it’s compiling a single project), and reports timings for all tasks run during build. The following table shows the time it took for the Fsc task to complete, which is the F# compiler:

F# versionTime to compile (second, rounded)
F# 549 seconds
F# 4.768 seconds
F# 4.5101 seconds

There was a big improvement from F# 4.5 to F# 4.7, and another big jump with F# 5 as well!

Your own results may vary a bit depending on a variety of factors, but if you try this out yourself you should see a fairly similar spread. It’s also worth noting that everything else in the .NET toolchain has improved too, so it’s not just the F# compiler getting faster when you use F# 5 with .NET 5.

Improved compiler analysis for library authors

F# 5 also includes a compiler feature that checks XML documentation against the signatures they document if you opt into it. This can be turned on in a project file via the OtherFlags property:

This will warn when the following code is compiled because the name of the parameter in the XML documentation doesn't match the name of the parameter in source.

This is a helpful setting for library authors who want to ensure coherence between their libraries and documentation.

Preview features

F# 5 is also bringing along two new preview features. To use these, you'll need to set your <LangVersion>preview</LangVersion> like so:

Preview: reverse indexes

We decided to keep the ability to do reverse indexes in preview for F# 5. There are still some quirks to work out with respect to System.Range and System.Index interop that we want to get right before releaseing fully.

The syntax is ^idx. Here’s how you can an element 1 value from the end of a list:

You can also define reverse indexes for your own types. To do so, you’ll need to implement the following method:

GetReverseIndex: dimension: int -> offset: int

Here’s an example for the Span<'T> type:

We feel that these three enhancements will make slicing data types more convenient in F# moving forward. What do you think?

Preview: overloads of custom keywords in computation expressions

Computation expressions are a powerful feature for library and framework authors. They allow you to greatly improve the expressiveness of your components by letting you define well-known members and form a DSL for the domain you're working in.

Diego Esmerio and Ryan Riley contributed a design an implementation to allow for overloading custom keywords in computation expressions. This new feature allows code like the following to be written:

Prior to this change, you could write the InputBuilder type as it is, but you couldn't use it the way it's used in the previous example. Since overloads, optional parameters, and now System.ParamArray types are allowed, everything just works as you'd expect it to.

What's next

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

  1. Improving our OSS infrastructure
  2. Core F# tooling improvements
  3. Planning for the next F# version

Today, the F# development repository is quite a complex codebase. There are several test suites that were created at various points in history, one of which being particularly challenging to work with because it doesn't load in any IDE tooling. We feel that it's unfair to ask open source contributors to do things like add or update tests when this test suite is concerned, so we're migrating it to be modern. The codebase also builds two versions of the F# compiler, called the "desktop compiler" and the "CoreCLR compiler". From a technical standpoint, the "CoreCLR compiler" can be used to build any F# project targeting any .NET flavor on any OS. The only reason why we still build both is technical debt that we intend on paying off. From an open source contributor's standpoint, this should simplify things as well.

We also plan on making significant F# tooling improvements over time. We've already started this work. One of the first steps was to incorporate FSharp.Compiler.Service into our build and packaging infrastructure so that any consumers of this package (e.g., the Ionide plugin in VSCode) can simply add a NuGet feed and get nightly updates. Next, we'll work towards retiring the FSharp.Compiler.Private project, which is functionally equivalent to FSharp.Compiler.Service, so that all F# tooling consumes the same functionality in the same way. From there, we intend on working with the F# community to eventually harden the F# LSP implementation so that it powers all F# editor tooling (including Visual Studio). The long-term goal is that all F# tools have consistent behavior and feature availability, with individual editors adding their own "flavor" and unique feature set on top of that behavior so that F# programmers can pick the tool they like the most.

Additionally, we're continuing to invest in F# tooling for Visual Studio by improving performance and adding several features. One such feature is called "Inline Hints", which you can activate by pressing and holding a key command. This lets you see the inferred types for all declarations and the names of F# parameters when applied (as if they were named parameters):

F# inline hints

The intention behind a feature like this is to let you effectively ask the compiler what it's chosen to infer for your code at any time. We're interested in incorporating other features that make things more productive for F# developers.

Finally, we're planning the next version of the F# language. There are several things on the docket already:

  1. Finishing reverse indices to account for .NET Range and Index interop
  2. Finishing overloads for custom computation expressions
  3. Adding support for a task { } computation expression with state machine generation
  4. Enhancements to the F# type system to better support efforts like DiffSharp

The list for new F# language features is constantly evolving and new requirements come in and we adjust accordingly. However, we're still committed to the direction that we set out on with F# 5. We want to continue to make F# a delightful language for interactive and analytical programming. You can expect to see more goodness in the near future.

Cheers, and happy F# coding!

19 comments

Comments are closed. Login to edit/delete your existing comments

  • Тимофей Плотников
    Тимофей Плотников

    Congratulations on the release!

    With this release aimed for analytical (data) programming, will the language still improve for general use cases? I love F# and want to see its adoption everywhere .net shines, not only for analytics 🙂

    Once again congrats!

    • Phillip Carter
      Phillip CarterMicrosoft employee

      Thanks! This is the beginning of our journey to make analytical and interactive programming great with F#. With that are features that are just generally good to use, but there is some emphasis placed on this domain. Just like how in previous releases, our primary focus was F# on .NET Core. That didn’t prevent generally awesome F# features from being built, but it did mean that we spent more time on .NET Core bring-up than other areas.

      In short, yes – F# will continue to have lots of great new improvements that every F# programmer can enjoy, not just those doing analytical and interactive programming.

  • Gauthier Segay
    Gauthier Segay

    For anyone interested in the reference extension mechanism to hook into #r “myextension: …”, sample implementations can be found:

    https://github.com/dotnet/fsharp/tree/main/src/fsharp/FSharp.DependencyManager.Nuget
    https://github.com/fsprojects/Paket/tree/master/src/FSharp.DependencyManager.Paket

    The paket extension ships in the v6 alpha releases, unpack the assembly in the folder where fsi.exe sits or give the folder path containing the extension as –compilertool argument when you call fsi.exe or through FSharp.Compiler.Service.

  • Avatar
    Joshua Dredge

    Looks great, lots of cool stuff!

    F# desperately needs access to the Windows Form Designer or WPF designer to make it super easy to build GUIs with. It always feels so unloved compared to C# and VB. If it wasn’t for the pain of designing a UI, I’d switch over to F# immediately. F# has such great potential in wayyy more areas than just crunching data.

    • Avatar
      Per Söderlund

      Well, you can create a wpf project with C#. Don’t touch the code files, design your UI as usual with XAML. By using MVVM pattern you can write your viewmodels and logic in F# library. That way you have full wpf designer and all code in F#. I havent used winforms in a while but it should be possible there as well by using databinding?

      • Avatar
        Tyson Williams

        I can second that recommendation for WPF. Furthermore, I recommend using Elmish.WPF. The repo contains many samples and there is a comprehensive tutorial. I am one of the maintainers, so if you have any questions, I will do my best to help.

  • Avatar
    Tyson Williams

    Great work everyone! I love the improvements.

    My biggest issue with F# is convincing others to use it. By definition, these people are new to F#, so they have a difficult time understanding what is possible. In C# when using Visual Studio, they can put their cursor caret on a type or type member contained in a dependency and push F12. This takes them to a page that not only shows them more documentation for the item under the caret than is displayed by the tooltip on hover but also includes all the members on the type in question.

    In F#, pushing F12 produces a popup that says “Cannot navigate to the symbol under the caret”.

    The most important dependency is FSharp.Core. In this case, the best alternative I have found is to use the F# Core Library Documentation (community edition). The two downsides with this approach are that it is slower than having matadata available in Visual Studio after a single button press and (last I checked) Google doesn’t returns hits from that site as well as it does when searching for the documentation of the .NET API.

    This issue seems to be the only one that covers this behavior but it requests the solution be to show decompiled source, which is going further than just displaying metadata as is the behavior for C#.

    It is frustrating for me to see that the top priorities for F# all involve adding new features. I would like if this lack of tooling issue had a higher priority.

  • Trevor Nederlof
    Trevor Nederlof

    I come from a data analytics background where I have used R, Python, Julia, and SQL for most of my tasks. Over the past 5 years I have played with F# and have appreciated so many aspects of the language but the lack of tooling and data analytical libraries have been quite the barriers. I really feel like the interactive notebook is a game-changer in this regard. I was able to open up a notebook, try some F# code out from F# for Fun and Profit, learn some key concepts, and even build up a dataframe. As these tools mature I could see it being much more likely that I would try to teach coworkers and other beginners to get them into the language. The tooling is looking so much better than when I checked out the language a few years prior where even loading in a CSV and trying to do group operations would give headaches, etc.

    Anyways, I just wanted to share just how amazing I find the interactive notebooks (especially for learnings/teaching beginners the language) and I really like the direction you and the team are headed Phillip. Thank you!

  • Avatar
    frank nitty

    I just tried to leave a feedback in the F# 5 announcements inside MS docs. The “developer experience” as you love to say at MS is horrible ^ 3. After a good while it turns out the offered feedback button at github does not allow anyone to give feedback. Holy Shit.

    The feedback and webpage things at MS do not go better after 25 years of internet. Offering a github-Feedback that then leads to a – we dont accept your feedback anyway haha – is not an overly great “experience”.

    Here goes, it relates to
    https://docs.microsoft.com/en-us/dotnet/fsharp/whats-new/fsharp-50

    [Enter feedback here]

    The intro reads

    “F# 5.0 adds several improvements to the F# language and F# Interactive. It is released with .NET 5.”

    It is then confusing that F# Interactive inside Visual Studio, 6 months after this post was written, still runs in dotnet 4 only.
    I suggest to clarify these points:
    – F# Interactive inside Visual Studio (with script debugging): When will it work with .Net 5?
    – Outside of VS: Is it possible to run fsi with dotnet 5? How? (I installed VS 219 16 9 and fsi still points to a dotnet 4 based version 14.0.23413.0)

    • Phillip Carter
      Phillip CarterMicrosoft employee

      Sorry you weren’t able to create an issue on GitHub; the system should just open a new tab for filing a new issue on the docs GitHub repo and have a correlation ID associated with the article in there for linkability. Do you have a GitHub account?

      Separately, VS tooling for F# Interactive is different from F# Interactive. Yes, currently VS is locked on .NET Framework. There’s a bunch of complicated reasons for it. In the upcoming VS 16.9 release we’re planning on shipping .NET 5-based F# Interactive support in the VS tools.

      Docs on how to use F# Interactive independently of a given editor (including launching dotnet fsi) can be found here: https://docs.microsoft.com/en-us/dotnet/fsharp/tools/fsharp-interactive/