post_title | username | microsoft_alias | featured_image | categories | summary | desired_publication_date |
---|---|---|---|---|---|---|
What’s new in F# 7 |
fsharp512.png |
F#, .NET, .NET Core |
F# 7 is now released |
2022-11-15 |
We’re happy to announce the availability of F# 7, shipping with .NET 7 and Visual Studio 2022. Check out the next step to making it easier for you to write robust, succinct and performant code. You can get F# 7 in the following ways:
- Install the latest .NET 7 SDK
- Install Visual Studio 2022 preview
- Use .NET Polyglot Notebooks in Jupyter or VS Code
F# 7 continues the journey to make F# simpler and more performant as well as improving interop with new C# features. Syntax for the F# specific feature SRTP is simplified. Support has been added for Static abstract members support in interfaces and consumption of C# required
members and init
scope. The F# compiler is improved to offer reference assemblies, trimmability, better code gen, ARM64 support, performance enhancements and bug fixes.
To learn more about F#, see Interview with F# designer Don Syme (part 1 / part 2), Betatalks #66 and previous year's The .NET Conf Focus Day on F#.
If you're interested in more advanced topics, see Fast F# series by Matthew Crews, F# community compiler sessions.
To stay up to date, check out Sergey Tihon's F# weekly, follow @fsharporg on Twitter, and join the F# Software Foundation and F# community to get the latest news and updates.
Static abstract members support in interfaces is a new feature of .NET 7. One notable application is generic math.
We are adding a way of declaring, calling and implementing static abstract members in interfaces.
First, let's declare an interface with a static abstract member:
type IAddition<'T when 'T :> IAdditionOperator<'T>> =
static abstract op_Addition: 'T * 'T -> 'T
Note The code above will produce
FS3535
warning -Declaring "interfaces with static abstract methods" is an advanced feature. See <https://aka.ms/fsharp-iwsams> for guidance. You can disable this warning by using '#nowarn "3535"' or '--nowarn:3535'.
Next, we can implement it ('T * 'T
is the F# signature for a function with two parameters of type `T):
type IAddition<'T when 'T :> IAddition<'T>> =
static abstract op_Addition: 'T * 'T -> 'T
type Number<'T when IAddition<'T>>(value: 'T) =
member _.Value with get() = value
interface IAddition<Number<'T>> with
static member op_Addition(a, b) = Number(a.Value + b.Value)
This will allow us to write generic functions that can be used with any type that implements IAddition
interface:
let add<'T when IAddition<'T>>(x: 'T) (y: 'T) = 'T.op_Addition(x,y)
or in the operator form:
let add<'T when IAddition<'T>>(x: 'T) (y: 'T) = x + y
This is a runtime feature and can be used with any static methods or properties, you can see a few more examples below.
type ISinOperator<'T when 'T :> ISinOperator<'T>> =
static abstract Sin: 'T -> 'T
let sin<'T when ISinOperator<'T>>(x: 'T) = 'T.Sin(x)
This feature can also be used with BCL built-in types (such as INumber<'T>
), for example:
open System.Numerics
let sum<'T, 'TResult when INumber<'T> and INumber<'TResult>>(values: 'T seq) =
let mutable result = 'TResult.Zero
for value in values do
result <- result + 'TResult.CreateChecked(value)
result
These simplified examples show how static abstract members in interfaces work. You are most likely to use this feature if you are a library author or you write specialized arithmetic functions.
Note
SRTPs or Statically Resolved Type Parameters are type parameters that are replaced with an actual type at compile time instead of at run time. F# 7 simplifis the syntax used for defining SRTPs.
As a quick refreshment on what SRTPs are, and how they're used, let's declare a function named average
that takes an array of type T
where type T
is a type that has at least the following members:
- An addition operator (
(+)
orop_Addition
) - A
DivideByInt
static method, which takes aT
and anint
and returns aT
- A static property named
Zero
that returns aT
let inline average< ^T
when ^T: (static member (+): ^T * ^T -> ^T)
and ^T: (static member DivideByInt : ^T * int -> ^T)
and ^T: (static member Zero : ^T)>
(xs: ^T array) =
let mutable sum : ^T = (^T : (static member Zero: ^T) ())
for x in xs do
sum <- (^T : (static member op_Addition: ^T * ^T -> ^T) (sum, x))
(^T : (static member DivideByInt: ^T * int -> ^T) (sum, xs.Length))
^T
here is a type parameter, and ^T: (static member (+): ^T * ^T -> ^T)
, ^T: (static member DivideByInt : ^T * int -> ^T)
, and ^T: (static member Zero : ^T)
are constraints for it.
We are making some improvements.
You no longer need to use a dedicated type parameter character (^
), a single tick character ('
) can be used instead, compiler will decide whether it's static or generic based on the context - whether the function is `inline`` and if it has constraints.
We are also adding a new simpler syntax for calling constraints, which is more readable and easier to write:
let inline average<'T
when 'T: (static member (+): 'T * 'T -> 'T)
and 'T: (static member DivideByInt : 'T * int -> 'T)
and 'T: (static member Zero : 'T)>
(xs: 'T array) =
let mutable sum = 'T.Zero
for x in xs do
sum <- sum + x
'T.DivideByInt(sum, xs.Length)
Finally, we've added the ability to declare constraints in groups:
type AverageOps<'T when 'T: (static member (+): 'T * 'T -> 'T)
and 'T: (static member DivideByInt : 'T*int -> 'T)
and 'T: (static member Zero : 'T)> = 'T
And a simpler syntax for self-constraints, which are constraints that refer to the type parameter itself:
let inline average<'T when AverageOps<'T>>(xs: 'T array) =
let mutable sum = 'T.Zero
for x in xs do
sum <- sum + x
'T.DivideByInt(sum, xs.Length)
Simplified call syntax also works with instance members:
type Length<'T when 'T: (member Length: int)> = 'T
let inline len<'T when Length<'T>>(x: 'T) =
x.Length
We believe that those changes will make working with SRTPs easier and more readable.
C# 11 introduced new required modifier for properties, F# 7 supports consuming classes with required properties and enforcing the constraint:
Consider the following data type, defined in the C# library:
public sealed class Person
{
public required string Name { get; set; }
public required string Surname { get; set; }
}
When using from F# code, compiler will make sure required properties are getting properly initialized:
let person = Person(Name = "John")
The code above will compile correctly, but if we try to omit any of the required properties, we will get a compile-time diagnostic:
FS3545: The following required properties have to be initalized:
property Person.Surname: string with get, set
In F# 7 we are tightening the rules for init-only
properties so that they can only be initialized within the init
scope. This is a new compile-time check and is a breaking change, and will only be applied starting F# 7, see examples below.
Given the following C# data type:
public sealed class Person
{
public int Id { get; init; }
public int Name { get; set; }
public int Surname { get; set; }
public Person Set() => this;
}
Before, in F# 6, the following code would've compiled and mutated the property Id
of the Person
:
let person = Person(Id = 42, Name = "John", Surname = "Doe")
person.Id <- 123
person.set_Id(123)
person.Set(Id=123)
In F# 7, we are changing this to be a compile-time error:
Error FS0810 Init-only property 'Id' cannot be set outside the initialization code. See https://aka.ms/fsharp-assigning-values-to-properties-at-initialization
Error FS0810 Cannot call 'set_Id' - a setter for init-only property, please use object initialization instead. See https://aka.ms/fsharp-assigning-values-to-properties-at-initialization
Error FS0810 Init-only property 'Id' cannot be set outside the initialization code. See https://aka.ms/fsharp-assigning-values-to-properties-at-initialization
Starting F# 7, compiler can generate and properly consume reference assemblies.
You can generate reference assemblies by:
- Adding
ProduceReferenceAssembly
MSBuild project property to yourfsproj
(<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
) file or msbuild flags (/p:ProduceReferenceAssembly=true
). - Using
--refout
or--refonly
compiler options.
We are looking forward to your feedback on this feature.
Other changes in F# 7 include:
- Added support for N-d arrays up to rank 32.
- Result module functions parity with Option.
- Fixes in resumable state machines codegen for the tasks builds.
- Better codegen for compiler-generated side-effect-free property getters.
- For better trimmability, reflection-free codegen flag for F# compiler `--reflectionfree":
- Skip emitting "%A" ToString implementation for records, unions and structs.
- Trimmabillity improvements for F# Core - added
ILLink.LinkAttributes.xml
andILLink.LinkAttributes.xml
forFSharp.Core
, which allows trimming compile-time resources and attributes for runtime-only FSharp.Core dependency. - ARM64 platform-specific compiler and ARM64 target support in F# compiler. Dependency manager
#r
caching support. - Parallel type-checking and project-checking support (experimental, can be enabled via VS setting, or by tooling authors).
- Miscellaneous bugfixes and improvements.
RFCs for F# 7 can be found here.
.NET 7 brought a myriad improvements, which F# 7 will benefit from - various arm64 performance improvements, and general performance improvements.
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# 7, including all who have contributed to the F# Language Design Process through fsharp/fslang-suggestions and fsharp/fslang-design repositories.
Adam Boniecki, Brett V. Forsgren, Don Syme, Jon Sequeira Kevin Ransom, Petr Pokorný, Petr Semkin, Tomáš Grošup, Vlad Zarytovskii, Will Smith contributed directly as part of F# team at Microsoft.
Florian Verdonck contributed 72 PRs, including work on parallel type-checking improvements, tons of improvements to the syntax trees, which benefit all tooling in general and Fantomas source code formatter in particular.
Eugene Auduchinok contributed [26 PRs including a bunch of improvements and optimizations in the parser, IL reading, completions, compiler service caching and more.
Our new contributor Edgar Gonzalez 🥳 contributed 17 PRs with tons of improvements and fixes around attributes processing, RequiredQualifiedAccess & lower-cased DU cases relaxations and whole bunch of bugfixes.
kerams contibuted 13 PRs for records, types, unions, enums, lambda variables completions, IL gen improvements, and more.
Other direct contributors to the dotnet/fsharp repository in the F# 7.0 time period include teo-tsirpanis, DedSec256, baronfel, leolorenzoluis, dawedawe, tmat, marcin-krystianc, cartermp, pirrmann, safesparrow, omppye, Happypig375, ncave, thinkbeforecoding, MichaelSimons, tboby, mmitche, nosami, jonfortescue, smoothdeveloper, abelbraaksma, uxsoft.
Many other people are contributing to the rollout of F# 7 and .NET 7 in Fable, Ionide, Bolero, FSharp.Data, Giraffe, Saturn, SAFE Stack, WebSharper, FsCheck, DiffSharp, Fantomas and other community-delivered technologies.
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 Edgar Gonzalez, and I live between Albacete(Spain) and London(Uk), where I'm a Mobile Developer at Fund Ourselves.
I'm new to the .NET ecosystem. Previously I was a member of the Spanish Paratroopers Brigade "Almogávares" VI, where I served as a Corporal first class (2004-2017).
I started programming five years ago, interested in mobile development with C#, and around the same time, I was introduced to F# by Frank A. Krueger using Xamarin.iOS
Last year I moved to F#, and during my journey realized that there is room for improvement regarding compiler error reporting, so I decided to get involved and help make F# simple for newcomers like me.
Why do I like F#? it has a clean and lightweight syntax, is expression-oriented, exhaustive pattern matching, and discriminated unions.
I look forward to continuing to help F# to be even better, focusing on making it easy for newcomers.
Thanks to Timothé Larivière and Florian Verdonck for helping me get started with open source.
I’m Florian Verdonck, an independent software consultant with a passion for open-source development. My F# open-source journey started in 2017, as part of the F# foundation mentorship program. My mentor, Anthony Lloyd, and I started contributing to the Fantomas project. Maintaining led to contributing, and later, I was adding to F# editor and compiler tooling. Several great mentors helped me get my contributions accepted and my pull requests merged in and I’m grateful to all of them.
Being inspired to contribute to the wonderful F# community is one thing. Having the resources to do it properly is another. Luckily, I found customers that share my vision of improving open source by active collaboration.
I’ve been fortunate to work with the open-source division of G-Research, a leading quantitative finance research firm. It is actively contributing to the open-source software it uses in-house. The leaders there believe that targeted open-source efforts can improve the operational efficacy of their engineers and encourage me in my work.
TODO: Florian
and a simpler syntax for self-constraints