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:
- Interactive programming
- Making analytical-oriented programming convenient and fun
- 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 .dll
s. 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:
It also supports inline charting:
And allows you to import and export 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
xy | 0 | 1 |
---|---|---|
0 | 0 | 1 |
1 | 2 | 3 |
z = 1
xy | 0 | 1 |
---|---|---|
0 | 4 | 5 |
1 | 6 | 7 |
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% fastercount
– up to 50% fasteriter
– up to 15% fasteradd
– up to 40% fasterremove
– up to 33% faster
For Set
, the improved operations are:
containsKey
– up to 40% fasterisSubsetOf
– up to 40% fastermax
– up to 50% fastercount
– up to 50% fasteradd
– up to 30% fasterremove
– 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# version | Time to compile (second, rounded) |
---|---|
F# 5 | 49 seconds |
F# 4.7 | 68 seconds |
F# 4.5 | 101 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:
- Improving our OSS infrastructure
- Core F# tooling improvements
- 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):
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:
- Finishing reverse indices to account for .NET
Range
andIndex
interop - Finishing overloads for custom computation expressions
- Adding support for a
task { }
computation expression with state machine generation - 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!
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 -...
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...
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,...
Is there any note on if F# will gain type classes / concepts?
They’re gated by C#, because they don’t want to introduce incompatibilities between the two languages (a la Async/Task).
Being discussed here: https://github.com/dotnet/csharplang/issues/110
Personally, I think it’ll be here no earlier than C#11 – two releases from now. But I’m just a random person on the internet, so what do I know 🙂
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...
Also, in VS Code, after creating a new C# project, I can instantly use C# 9 features such as Records.
But after creating a new F# project, I cannot use F# 5 features such as String Interpolation. I get this: "Feature 'string interpolation' is not available in F# 4.7. Please use language version 'preview' or greater." Why? And how do I fix it? How can I make F# 5 the default language version, like C# 9...
Currently the Ionide plugin requires Ionide 5 preview to support F# 5 syntax in the language service: https://github.com/ionide/ionide-vscode-fsharp/releases
Here is my understanding. All F# code depends on FSharp.Core. By default, this dependency is implicit. You can make this explicit just like you do for any other NuGet package. Alternatively, if you build your code with the .NET 5 SDK, then the version of the implicit dependency on FSharp.Core will be 5.0.0.
I agree 100%
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.
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?
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.
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.
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!
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...
great job!
Thanks!