| title | What's new in F# 7 - F# Guide |
|---|---|
| description | Find information on the new features available in F# 7. |
| ms.date | 11/17/2023 |
| ms.topic | whats-new |
| ai-usage | ai-assisted |
F# 7 introduces enhancements that make F# simpler and more performant while improving interop with new C# features. This article highlights the major changes in F# 7, developed in the F# open source code repository.
F# 7 is available in .NET 7. You can download the latest .NET SDK from the .NET downloads page.
Static abstract members in interfaces is a new feature of .NET 7. One notable application is generic math.
F# 7 adds the ability to declare, call, and implement static abstract members in interfaces.
First, declare an interface with a static abstract member:
type IAddition<'T when 'T :> IAddition<'T>> =
static abstract op_Addition: 'T * 'T -> 'TNote
The code above produces the 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, implement it:
type Number(value: int) =
member _.Value = value
interface IAddition<Number> with
static member op_Addition(a, b) = Number(a.Value + b.Value)This allows you to write generic functions that can be used with any type that implements the IAddition interface:
let add<'T when IAddition<'T>>(x: 'T) (y: 'T) = 'T.op_Addition(x, y)Or in operator form:
let add<'T when IAddition<'T>>(x: 'T) (y: 'T) = x + yThis is a runtime feature and can be used with any static methods or properties. Here's another example:
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>:
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)
resultThese examples show how static abstract members in interfaces work. You're most likely to use this feature if you're a library author or you write specialized arithmetic functions.
Statically Resolved Type Parameters (SRTPs) are type parameters that are replaced with an actual type at compile time instead of at run time. F# 7 simplifies the syntax used for defining SRTPs.
As a quick refresher 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 has at least the following members:
- An addition operator (
(+)orop_Addition) - A
DivideByIntstatic method that takes aTand anintand returns aT - A static property named
Zerothat returns aT
Previously, you had 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 = (^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))You no longer need to use a dedicated type parameter character (^). A single tick character (') can be used instead. The compiler decides whether it's static or generic based on the context—whether the function is inline and if it has constraints.
F# 7 also adds 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)F# 7 adds 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)> = 'TAnd 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.LengthThese changes make working with SRTPs easier and more readable.
C# 11 introduced a new required modifier for properties. F# 7 supports consuming classes with required properties and enforcing the constraint.
Consider the following data type, defined in a C# library:
public sealed class Person
{
public required string Name { get; set; }
public required string Surname { get; set; }
}When using this type from F# code, the compiler makes sure required properties are properly initialized:
let person = Person(Name = "John", Surname = "Doe")If you try to omit any of the required properties, you get a compile-time diagnostic:
let person = Person(Name = "John")
// FS3545: The following required properties have to be initialized:
// property Person.Surname: string with get, setIn F# 7, the rules for init-only properties are tightened so that they can only be initialized within the init scope. This is a new compile-time check and is a breaking change that only applies starting in F# 7.
Given the following C# data type:
public sealed class Person
{
public int Id { get; init; }
public string Name { get; set; }
public string Surname { get; set; }
public Person Set() => this;
}Previously, in F# 6, the following code would compile and mutate 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, this is a compile-time error:
// Error FS0810: Init-only property 'Id' cannot be set outside the initialization code.
// Error FS0810: Cannot call 'set_Id' - a setter for init-only property, please use object initialization instead.
// Error FS0810: Init-only property 'Id' cannot be set outside the initialization code.Starting with F# 7, the compiler can generate and properly consume reference assemblies.
You can generate reference assemblies by:
- Adding the
ProduceReferenceAssemblyMSBuild project property to your.fsprojfile (<ProduceReferenceAssembly>true</ProduceReferenceAssembly>) or using MSBuild flags (/p:ProduceReferenceAssembly=true). - Using the
--refoutor--refonlycompiler options.
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, a reflection-free codegen flag for the F# compiler (
--reflectionfree):- Skips emitting
%AToString implementation for records, unions, and structs.
- Skips emitting
- Trimmability improvements for FSharp.Core—added
ILLink.LinkAttributes.xmlforFSharp.Core, which allows trimming compile-time resources and attributes for runtime-only FSharp.Core dependency. - ARM64 platform-specific compiler and ARM64 target support in the F# compiler.
- Dependency manager
#rcaching support. - Parallel type-checking and project-checking support (experimental, can be enabled via Visual Studio setting or by tooling authors).
- Miscellaneous bug fixes and improvements.
RFCs for F# 7 can be found in the F# language design repository.
.NET 7 brought many improvements that F# 7 benefits from, including various ARM64 performance improvements and general performance improvements.