-
Notifications
You must be signed in to change notification settings - Fork 6.1k
Create comprehensive F# 8 What's New documentation from blog post #50100
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
Copilot
wants to merge
4
commits into
main
Choose a base branch
from
copilot/create-whats-new-f8-page
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+581
−1
Draft
Changes from 3 commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,7 +3,392 @@ | |
| description: Find information on the new features available in F# 8. | ||
| ms.date: 11/17/2023 | ||
| ms.topic: whats-new | ||
| ai-usage: ai-assisted | ||
| --- | ||
| # What's new in F# 8 | ||
|
|
||
| For information on F# 8, please see [Announcing F# 8](https://devblogs.microsoft.com/dotnet/announcing-fsharp-8). | ||
| F# 8 brings in many features to make F# programs simpler, more uniform, and more performant. This article highlights the major changes in F# 8, developed in the [F# open source code repository](https://github.com/dotnet/fsharp). | ||
|
|
||
| F# 8 is released as part of .NET 8. You can download the latest .NET SDK from the [.NET downloads page](https://dotnet.microsoft.com/download/dotnet/8.0). | ||
|
|
||
| For the full announcement with in-depth details and community contributions, see [Announcing F# 8](https://devblogs.microsoft.com/dotnet/announcing-fsharp-8). | ||
|
|
||
| ## F# language changes | ||
|
|
||
| ### `_.Property` shorthand for simple lambda functions | ||
|
|
||
| F# 8 introduces a succinct notation for simple lambda functions that access properties or members. Instead of writing `(fun x -> x.Property)`, you can now write `_.Property`. | ||
|
|
||
| **Before F# 8:** | ||
|
|
||
| :::code language="fsharp" source="snippets/fsharp-8/PropertyShorthand.fsx" id="Types"::: | ||
|
|
||
| :::code language="fsharp" source="snippets/fsharp-8/PropertyShorthand.fsx" id="Before"::: | ||
|
|
||
| **With F# 8:** | ||
|
|
||
| :::code language="fsharp" source="snippets/fsharp-8/PropertyShorthand.fsx" id="After"::: | ||
|
|
||
| This shorthand works for property access, nested property access, method calls, and indexers. You can also define standalone lambda functions: | ||
|
|
||
| :::code language="fsharp" source="snippets/fsharp-8/PropertyShorthand.fsx" id="Standalone"::: | ||
|
|
||
| ### Nested record field copy and update | ||
|
|
||
| F# 8 extends the copy-and-update syntax for nested records, eliminating the need for multiple nested `with` keywords. | ||
|
|
||
| **Before F# 8:** | ||
|
|
||
| :::code language="fsharp" source="snippets/fsharp-8/NestedRecordUpdate.fsx" id="Types"::: | ||
|
|
||
| :::code language="fsharp" source="snippets/fsharp-8/NestedRecordUpdate.fsx" id="Before"::: | ||
|
|
||
| **With F# 8:** | ||
|
|
||
| :::code language="fsharp" source="snippets/fsharp-8/NestedRecordUpdate.fsx" id="After"::: | ||
|
|
||
| This syntax also works for anonymous records: | ||
|
|
||
| :::code language="fsharp" source="snippets/fsharp-8/NestedRecordUpdate.fsx" id="Anonymous"::: | ||
|
|
||
| ### while! | ||
|
|
||
| The `while!` (while bang) feature simplifies computation expressions by eliminating boilerplate when looping over a boolean condition that must be evaluated by the computation expression. | ||
|
|
||
| **Before F# 8:** | ||
|
|
||
| ```fsharp | ||
| let mutable count = 0 | ||
| let asyncCondition = async { | ||
| return count < 10 | ||
| } | ||
| let doStuffBeforeThisFeature = | ||
| async { | ||
| let! firstRead = asyncCondition | ||
| let mutable read = firstRead | ||
| while read do | ||
| count <- count + 2 | ||
| let! nextRead = asyncCondition | ||
| read <- nextRead | ||
| return count | ||
| } | ||
| ``` | ||
|
|
||
| **With F# 8:** | ||
|
|
||
| ```fsharp | ||
| let doStuffWithWhileBang = | ||
| async { | ||
| while! asyncCondition do | ||
| count <- count + 2 | ||
| return count | ||
| } | ||
| ``` | ||
|
|
||
| For more details, see [Simplifying F# computations with the new while! keyword](https://devblogs.microsoft.com/dotnet/simplifying-fsharp-computations-with-the-new-while-keyword/). | ||
|
|
||
| ### Extended string interpolation syntax | ||
|
|
||
| F# 8 improves support for interpolated strings, taking inspiration from C# interpolated raw string literals. You can now use multiple `$` signs at the beginning of an interpolated string to define how many braces are needed to enter interpolation mode. | ||
|
|
||
| **Before F# 8:** | ||
|
|
||
| :::code language="fsharp" source="snippets/fsharp-8/StringInterpolation.fsx" id="Before"::: | ||
|
|
||
| **With F# 8:** | ||
|
|
||
| :::code language="fsharp" source="snippets/fsharp-8/StringInterpolation.fsx" id="After"::: | ||
|
|
||
| This is especially useful for HTML templating languages: | ||
|
|
||
| :::code language="fsharp" source="snippets/fsharp-8/StringInterpolation.fsx" id="Template"::: | ||
|
|
||
| For more details, see [New syntax for string interpolation in F# 8](https://devblogs.microsoft.com/dotnet/new-syntax-for-string-interpolation-in-fsharp/). | ||
|
|
||
| ### Use and compose string literals for printf | ||
|
|
||
| String literals can now be used and composed for `printf` and related functions. Format specifiers defined as literals can be reused as patterns. | ||
|
|
||
| ```fsharp | ||
| [<Literal>] | ||
| let formatBody = "(%f,%f)" | ||
| [<Literal>] | ||
| let formatPrefix = "Person at coordinates" | ||
| [<Literal>] | ||
| let fullFormat = formatPrefix + formatBody | ||
| let renderedCoordinates = sprintf formatBody 0.25 0.75 | ||
| let renderedText = sprintf fullFormat 0.25 0.75 | ||
| ``` | ||
|
|
||
| ### Arithmetic operators in literals | ||
|
|
||
| Numeric literals can now be expressed using existing operators and other literals. The compiler evaluates the expression at compile time. | ||
|
|
||
| :::code language="fsharp" source="snippets/fsharp-8/LiteralArithmetic.fsx" id="Literals"::: | ||
|
|
||
| Supported operators: | ||
| - Numeric types: `+`, `-`, `*`, `/`, `%`, `&&&`, `|||`, `<<<`, `>>>`, `^^^`, `~~~`, `**` | ||
|
Check failure on line 132 in docs/fsharp/whats-new/fsharp-8.md
|
||
| - Booleans: `not`, `&&`, `||` | ||
|
|
||
| The feature also works for enum values: | ||
|
|
||
| :::code language="fsharp" source="snippets/fsharp-8/LiteralArithmetic.fsx" id="Enums"::: | ||
|
|
||
| ### Type constraint intersection syntax | ||
|
|
||
| F# 8 simplifies defining multiple intersected generic constraints using flexible types with the `&` character. | ||
|
|
||
| **Before F# 8:** | ||
|
|
||
| ```fsharp | ||
| let beforeThis(arg1 : 't | ||
| when 't:>IDisposable | ||
| and 't:>IEx | ||
| and 't:>seq<int>) = | ||
| arg1.h(arg1) | ||
| arg1.Dispose() | ||
| for x in arg1 do | ||
| printfn "%i" x | ||
| ``` | ||
|
|
||
| **With F# 8:** | ||
|
|
||
| ```fsharp | ||
| let withNewFeature (arg1: 't & #IEx & #IDisposable & #seq<int>) = | ||
| arg1.h(arg1) | ||
| arg1.Dispose() | ||
| for x in arg1 do | ||
| printfn "%i" x | ||
| ``` | ||
|
|
||
| ### Extended fixed bindings | ||
|
|
||
| The `fixed` keyword, used for pinning memory in low-level programming, now supports: | ||
|
|
||
| - `byref<'t>` | ||
| - `inref<'t>` | ||
| - `outref<'t>` | ||
| - Types with `GetPinnableReference` method returning `byref<'t>` or `inref<'t>` | ||
|
|
||
| This is especially relevant for types like `ReadOnlySpan<'T>` and `Span<'T>`. | ||
|
|
||
| ```fsharp | ||
| open System | ||
| open FSharp.NativeInterop | ||
| #nowarn "9" | ||
| let pinIt (span: Span<char>, byRef: byref<int>, inRef: inref<int>) = | ||
| use ptrSpan = fixed span | ||
| use ptrByRef = fixed &byRef | ||
| use ptrInref = fixed &inRef | ||
| NativePtr.copyBlock ptrByRef ptrInref 1 | ||
| ``` | ||
|
|
||
| ### Easier `[<Extension>]` method definition | ||
|
|
||
| The `[<Extension>]` attribute no longer needs to be applied to both the type and the member. The compiler automatically adds the type-level attribute. | ||
|
|
||
| **Before F# 8:** | ||
|
|
||
| ```fsharp | ||
| open System.Runtime.CompilerServices | ||
| [<Extension>] | ||
| type Foo = | ||
| [<Extension>] | ||
| static member PlusOne (a:int) : int = a + 1 | ||
| ``` | ||
|
|
||
| **With F# 8:** | ||
|
|
||
| ```fsharp | ||
| open System.Runtime.CompilerServices | ||
| type Foo = | ||
| [<Extension>] | ||
| static member PlusOne (a:int) : int = a + 1 | ||
| ``` | ||
|
|
||
| ## Making F# more uniform | ||
|
|
||
| ### Static members in interfaces | ||
|
|
||
| Interfaces can now declare and implement concrete static members (not to be confused with static abstract members from F# 7). | ||
|
|
||
| **With F# 8:** | ||
|
|
||
| :::code language="fsharp" source="snippets/fsharp-8/StaticInInterfaces.fsx" id="Interface"::: | ||
|
|
||
| ### Static let in discriminated unions, records, structs, and types without primary constructors | ||
|
|
||
| F# 8 enables `static let`, `static let mutable`, `static do`, and `static member val` in: | ||
|
|
||
| - Discriminated unions | ||
| - Records | ||
| - Structs (including `[<Struct>]` unions and records) | ||
| - Types without primary constructors | ||
|
|
||
| :::code language="fsharp" source="snippets/fsharp-8/StaticLetInDU.fsx" id="StaticLet"::: | ||
|
|
||
| ### `try-with` within `seq{}`, `[]`, and `[||]` collection expressions | ||
|
|
||
| Exception handling is now supported within collection builders. | ||
|
|
||
| :::code language="fsharp" source="snippets/fsharp-8/TryWithInCollections.fsx" id="TryWith"::: | ||
|
|
||
| This yields the list `[1;1000;1;10;100]`, which sums to 1112. | ||
|
|
||
| ## New diagnostics | ||
|
|
||
| F# 8 adds many new and improved diagnostic messages. | ||
|
|
||
| ### TailCall attribute | ||
|
|
||
| Use the `[<TailCall>]` attribute to explicitly state your intention of defining a tail-recursive function. The compiler warns you if your function makes non-tail-recursive calls. | ||
|
|
||
| :::code language="fsharp" source="snippets/fsharp-8/TailCallAttribute.fsx" id="Classic"::: | ||
|
|
||
| :::code language="fsharp" source="snippets/fsharp-8/TailCallAttribute.fsx" id="Accumulator"::: | ||
|
|
||
| ### Diagnostics on static classes | ||
|
|
||
| New warnings detect invalid scenarios on sealed abstract classes (static classes): | ||
|
|
||
| - Instance let bindings aren't allowed | ||
| - Implementing interfaces isn't allowed | ||
| - Explicit field declarations aren't allowed | ||
| - Constructor with arguments isn't allowed | ||
| - Additional constructor isn't allowed | ||
| - Abstract member declarations aren't allowed | ||
|
|
||
| ### Diagnostics on `[<Obsolete>]` usage | ||
|
|
||
| Detection of obsolete members has been improved: | ||
|
|
||
| - Enum value usage | ||
| - Event usage | ||
| - Record copy-and-update syntax when the obsolete field is part of the update | ||
|
|
||
| ```fsharp | ||
| open System | ||
| type Color = | ||
| | [<Obsolete("Use B instead")>] Red = 0 | ||
| | Green = 1 | ||
| let c = Color.Red // warning at this line | ||
| ``` | ||
|
|
||
| ### Optional warning when `obj` is inferred | ||
|
|
||
| A new information-level diagnostic (FS3559) warns when type inference infers `obj` instead of a generic type, which might be unintended. | ||
|
|
||
| ```fsharp | ||
| ([] = []) // triggers the warning | ||
| ``` | ||
|
|
||
| Enable with `<WarnOn>FS3559</WarnOn>` in your project file. | ||
|
|
||
| ### Optional warning when copy and update changes all fields | ||
|
|
||
| A warning (FS3560) detects when copy-and-update syntax changes all fields of a record. It's more efficient to create a new record directly. | ||
|
|
||
| Enable with `<WarnOn>FS3560</WarnOn>` in your project file. | ||
|
|
||
| ## Quality of life improvements | ||
|
|
||
| ### Trimmability for compiler-generated code | ||
|
|
||
| F# 8 improves support for [.NET trimming](../../core/deploying/trimming/trim-self-contained.md): | ||
|
|
||
| - Discriminated unions are now trimmable | ||
| - Anonymous records are now trimmable | ||
| - Code using `printfn "%A"` for trimmed records is now trimmable | ||
|
|
||
| ### Parser recovery | ||
|
|
||
| Parser recovery has been vastly improved. IDE features like coloring and navigation now continue working even when code has syntactical errors or is incomplete. | ||
|
|
||
| For more details, watch [Parser Recovery in F#](https://amplifying-fsharp.github.io/sessions/2023/07/07/). | ||
|
|
||
| ### Strict indentation rules | ||
|
|
||
| F# 8 turns on strict indentation mode by default for projects targeting F# 8 and newer. This mode respects language rules for indentation and reports errors instead of warnings. | ||
|
|
||
| Configure using `--strict-indentation[+|-]` or in your project file: | ||
|
|
||
| ```xml | ||
| <OtherFlags>--strict-indentation-</OtherFlags> | ||
| ``` | ||
|
|
||
| ### Autocomplete improvements | ||
|
|
||
| Autocomplete scenarios have been improved: | ||
|
|
||
| - Record completions in patterns | ||
| - Union fields in patterns | ||
| - Return type annotations | ||
| - Method completions for overrides | ||
| - Constant value completions in pattern matching | ||
| - Expressions in enum values | ||
| - Names based on labels of union-case fields | ||
| - Collections of anonymous records | ||
| - Settable properties in attribute completions | ||
|
|
||
| ### `[<Struct>]` unions can now have > 49 cases | ||
|
|
||
| The limitation on struct unions with more than 49 cases has been removed, making them suitable for longer definitions. | ||
|
|
||
| ## Compiler performance | ||
|
|
||
| ### Reference assemblies | ||
|
|
||
| [Reference assemblies](../../standard/assembly/reference-assemblies.md) are now better utilized for F# projects, improving incremental build times by reducing unnecessary rebuilds when only implementation details change. | ||
|
|
||
| ### Switches for compiler parallelization | ||
|
|
||
| Three experimental features enable parallelization of different compilation stages: | ||
|
|
||
| - `--test:GraphBasedChecking` - Enables graph-based type checking for parallel processing | ||
| - `--test:ParallelOptimization` - Parallelizes the optimization phase | ||
| - `--test:ParallelIlxGen` - Parallelizes IL code generation | ||
|
|
||
| Read more in [Graph-based type-checking](https://devblogs.microsoft.com/dotnet/a-new-fsharp-compiler-feature-graphbased-typechecking/). | ||
|
|
||
| Enable all features using the `FSHARP_EXPERIMENTAL_FEATURES` environment variable: | ||
|
|
||
| ```powershell | ||
| $env:FSHARP_EXPERIMENTAL_FEATURES = '1' | ||
| ``` | ||
|
|
||
| ## Enhancements to FSharp.Core | ||
|
|
||
| ### Inlining improvements | ||
|
|
||
| F# 8 adds inlining optimizations to the `Option` and `ValueOption` modules, reducing allocations and improving performance. For example, mapping `None` now takes 0.17ns instead of 2.77ns (16× improvement). | ||
|
|
||
| ### `Array.Parallel.*` APIs | ||
|
|
||
| The `Array.Parallel` module has been expanded with many new functions: | ||
|
|
||
| - `exists`, `forAll`, `tryFindIndex`, `tryFind`, `tryPick` | ||
| - `reduceBy`, `reduce` | ||
| - `minBy`, `min`, `sumBy`, `sum`, `maxBy`, `max`, `averageBy`, `average` | ||
| - `zip`, `groupBy`, `filter` | ||
| - `sortInPlaceWith`, `sortInPlaceBy`, `sortInPlace` | ||
| - `sortWith`, `sortBy`, `sort`, `sortByDescending`, `sortDescending` | ||
|
|
||
| These functions are useful for CPU-intensive processing on large arrays and utilize all available logical processors. | ||
|
|
||
| ### `async` improvements | ||
|
|
||
| - `Bind` of `Async<>` within `task{}` now starts on the same thread | ||
| - `MailBoxProcessor` has a public `.Dispose()` member | ||
| - `MailBoxProcessor` now has `StartImmediate` to start on the calling thread | ||
|
|
||
| ## See also | ||
|
|
||
| - [F# Language Reference](../language-reference/index.md) | ||
| - [What's new in F# 9](fsharp-9.md) | ||
| - [What's new in F# 7](fsharp-7.md) | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.