From 4b56adab3f388df5d8278eb56e367c8f060f1554 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 24 Nov 2025 11:44:06 +0000
Subject: [PATCH 1/4] Initial plan
From d40c4cc784a49f80f5faae9adb31d5f5d02bfa29 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 24 Nov 2025 11:53:39 +0000
Subject: [PATCH 2/4] Add comprehensive F# 8 What's New documentation with
tested examples
Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com>
---
docs/fsharp/whats-new/fsharp-8.md | 486 +++++++++++++++++-
.../snippets/fsharp-8/LiteralArithmetic.fsx | 22 +
.../snippets/fsharp-8/NestedRecordUpdate.fsx | 34 ++
.../snippets/fsharp-8/PropertyShorthand.fsx | 32 ++
.../snippets/fsharp-8/StaticInInterfaces.fsx | 14 +
.../snippets/fsharp-8/StaticLetInDU.fsx | 24 +
.../snippets/fsharp-8/StringInterpolation.fsx | 18 +
.../snippets/fsharp-8/TailCallAttribute.fsx | 22 +
.../fsharp-8/TryWithInCollections.fsx | 13 +
9 files changed, 664 insertions(+), 1 deletion(-)
create mode 100644 docs/fsharp/whats-new/snippets/fsharp-8/LiteralArithmetic.fsx
create mode 100644 docs/fsharp/whats-new/snippets/fsharp-8/NestedRecordUpdate.fsx
create mode 100644 docs/fsharp/whats-new/snippets/fsharp-8/PropertyShorthand.fsx
create mode 100644 docs/fsharp/whats-new/snippets/fsharp-8/StaticInInterfaces.fsx
create mode 100644 docs/fsharp/whats-new/snippets/fsharp-8/StaticLetInDU.fsx
create mode 100644 docs/fsharp/whats-new/snippets/fsharp-8/StringInterpolation.fsx
create mode 100644 docs/fsharp/whats-new/snippets/fsharp-8/TailCallAttribute.fsx
create mode 100644 docs/fsharp/whats-new/snippets/fsharp-8/TryWithInCollections.fsx
diff --git a/docs/fsharp/whats-new/fsharp-8.md b/docs/fsharp/whats-new/fsharp-8.md
index 874086770ba1c..1f9aed227154b 100644
--- a/docs/fsharp/whats-new/fsharp-8.md
+++ b/docs/fsharp/whats-new/fsharp-8.md
@@ -3,7 +3,491 @@ title: What's new in F# 8 - F# Guide
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:**
+
+```fsharp
+type Person = {Name : string; Age : int}
+let people = [ {Name = "Joe"; Age = 20} ; {Name = "Will"; Age = 30} ; {Name = "Joe"; Age = 51}]
+
+let beforeThisFeature =
+ people
+ |> List.distinctBy (fun x -> x.Name)
+ |> List.groupBy (fun x -> x.Age)
+ |> List.map (fun (x,y) -> y)
+ |> List.map (fun x -> x.Head.Name)
+ |> List.sortBy (fun x -> x.ToString())
+```
+
+**With F# 8:**
+
+```fsharp
+let possibleNow =
+ people
+ |> List.distinctBy _.Name
+ |> List.groupBy _.Age
+ |> List.map snd
+ |> List.map _.Head.Name
+ |> List.sortBy _.ToString()
+```
+
+This shorthand works for property access, nested property access, method calls, and indexers. You can also define standalone lambda functions:
+
+```fsharp
+let ageAccessor : Person -> int = _.Age
+let getNameLength = _.Name.Length
+```
+
+### 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:**
+
+```fsharp
+type SteeringWheel = { Type: string }
+type CarInterior = { Steering: SteeringWheel; Seats: int }
+type Car = { Interior: CarInterior; ExteriorColor: string option }
+
+let beforeThisFeature x =
+ { x with Interior = { x.Interior with
+ Steering = {x.Interior.Steering with Type = "yoke"}
+ Seats = 5
+ }
+ }
+```
+
+**With F# 8:**
+
+```fsharp
+let withTheFeature x = { x with Interior.Steering.Type = "yoke"; Interior.Seats = 5 }
+```
+
+This syntax also works for anonymous records:
+
+```fsharp
+let alsoWorksForAnonymous (x:Car) = {| x with Interior.Seats = 7; Price = 99_999 |}
+```
+
+### 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:**
+
+```fsharp
+let classAttr = "item-panel"
+let cssOld = $""".{classAttr}:hover {{background-color: #eee;}}"""
+```
+
+**With F# 8:**
+
+```fsharp
+let cssNew = $$""".{{classAttr}}:hover {background-color: #eee;}"""
+```
+
+This is especially useful for HTML templating languages:
+
+```fsharp
+let templateNew = $$$"""
+
+"""
+```
+
+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
+[]
+let formatBody = "(%f,%f)"
+[]
+let formatPrefix = "Person at coordinates"
+[]
+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.
+
+```fsharp
+let [] bytesInKB = 2f ** 10f
+let [] bytesInMB = bytesInKB * bytesInKB
+let [] bytesInGB = 1 <<< 30
+let [] customBitMask = 0b01010101uy
+let [] inverseBitMask = ~~~ customBitMask
+```
+
+Supported operators:
+- Numeric types: `+`, `-`, `*`, `/`, `%`, `&&&`, `|||`, `<<<`, `>>>`, `^^^`, `~~~`, `**`
+- Booleans: `not`, `&&`, `||`
+
+### 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) =
+ arg1.h(arg1)
+ arg1.Dispose()
+ for x in arg1 do
+ printfn "%i" x
+```
+
+**With F# 8:**
+
+```fsharp
+let withNewFeature (arg1: 't & #IEx & #IDisposable & #seq) =
+ 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, byRef: byref, inRef: inref) =
+ use ptrSpan = fixed span
+ use ptrByRef = fixed &byRef
+ use ptrInref = fixed &inRef
+
+ NativePtr.copyBlock ptrByRef ptrInref 1
+```
+
+### Easier `[]` method definition
+
+The `[]` 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
+[]
+type Foo =
+ []
+ static member PlusOne (a:int) : int = a + 1
+```
+
+**With F# 8:**
+
+```fsharp
+open System.Runtime.CompilerServices
+type Foo =
+ []
+ 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).
+
+**Before F# 8:**
+
+```fsharp
+[]
+type IDemoableOld =
+ abstract member Show: string -> unit
+
+module IDemoableOld =
+ let autoFormat(a) = sprintf "%A" a
+```
+
+**With F# 8:**
+
+```fsharp
+[]
+type IDemoable =
+ abstract member Show: string -> unit
+ static member AutoFormat(a) = sprintf "%A" a
+```
+
+### 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 `[]` unions and records)
+- Types without primary constructors
+
+```fsharp
+open FSharp.Reflection
+
+type AbcDU = A | B | C
+ with
+ static let namesAndValues =
+ FSharpType.GetUnionCases(typeof)
+ |> Array.map (fun c -> c.Name, FSharpValue.MakeUnion (c,[||]) :?> AbcDU)
+ static let stringMap = namesAndValues |> dict
+ static let mutable cnt = 0
+
+ static do printfn "Init done! We have %i cases" stringMap.Count
+ static member TryParse text =
+ let cnt = System.Threading.Interlocked.Increment(&cnt)
+ stringMap.TryGetValue text, sprintf "Parsed %i" cnt
+```
+
+### `try-with` within `seq{}`, `[]`, and `[||]` collection expressions
+
+Exception handling is now supported within collection builders.
+
+```fsharp
+let sum =
+ [ for x in [0;1] do
+ try
+ yield 1
+ yield (10/x)
+ yield 100
+ with _ ->
+ yield 1000 ]
+ |> List.sum
+```
+
+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 `[]` attribute to explicitly state your intention of defining a tail-recursive function. The compiler warns you if your function makes non-tail-recursive calls.
+
+```fsharp
+[]
+let rec factorialClassic n =
+ match n with
+ | 0u | 1u -> 1u
+ | _ -> n * (factorialClassic (n - 1u))
+// This produces a warning
+
+[]
+let rec factorialWithAcc n accumulator =
+ match n with
+ | 0u | 1u -> accumulator
+ | _ -> factorialWithAcc (n - 1u) (n * accumulator)
+// This is a tail call and does NOT produce a warning
+```
+
+### 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 `[]` 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 =
+ | [] 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 `FS3559` 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 `FS3560` 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
+--strict-indentation-
+```
+
+### 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
+
+### `[]` 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)
diff --git a/docs/fsharp/whats-new/snippets/fsharp-8/LiteralArithmetic.fsx b/docs/fsharp/whats-new/snippets/fsharp-8/LiteralArithmetic.fsx
new file mode 100644
index 0000000000000..c75fd64713b38
--- /dev/null
+++ b/docs/fsharp/whats-new/snippets/fsharp-8/LiteralArithmetic.fsx
@@ -0,0 +1,22 @@
+// Arithmetic operators in literals
+let [] bytesInKB = 2f ** 10f
+let [] bytesInMB = bytesInKB * bytesInKB
+let [] bytesInGB = 1 <<< 30
+let [] customBitMask = 0b01010101uy
+let [] inverseBitMask = ~~~ customBitMask
+
+printfn "Bytes in KB: %f" bytesInKB
+printfn "Bytes in MB: %f" bytesInMB
+printfn "Bytes in GB: %d" bytesInGB
+printfn "Custom bit mask: %d" customBitMask
+printfn "Inverse bit mask: %d" inverseBitMask
+
+// Works for enum values
+type MyEnum =
+ | A = (1 <<< 5)
+ | B = (17 * 45 % 13)
+ | C = bytesInGB
+
+printfn "MyEnum.A: %d" (int MyEnum.A)
+printfn "MyEnum.B: %d" (int MyEnum.B)
+printfn "MyEnum.C: %d" (int MyEnum.C)
diff --git a/docs/fsharp/whats-new/snippets/fsharp-8/NestedRecordUpdate.fsx b/docs/fsharp/whats-new/snippets/fsharp-8/NestedRecordUpdate.fsx
new file mode 100644
index 0000000000000..cd001df798ddd
--- /dev/null
+++ b/docs/fsharp/whats-new/snippets/fsharp-8/NestedRecordUpdate.fsx
@@ -0,0 +1,34 @@
+// Nested record field copy and update
+type SteeringWheel = { Type: string }
+type CarInterior = { Steering: SteeringWheel; Seats: int }
+type Car = { Interior: CarInterior; ExteriorColor: string option }
+
+let myCar = {
+ Interior = {
+ Steering = { Type = "wheel" }
+ Seats = 4
+ }
+ ExteriorColor = Some "red"
+}
+
+// Before F# 8
+let beforeThisFeature x =
+ { x with Interior = { x.Interior with
+ Steering = {x.Interior.Steering with Type = "yoke"}
+ Seats = 5
+ }
+ }
+
+// With F# 8
+let withTheFeature x = { x with Interior.Steering.Type = "yoke"; Interior.Seats = 5 }
+
+let updatedCar1 = beforeThisFeature myCar
+let updatedCar2 = withTheFeature myCar
+
+printfn "Before: %A" updatedCar1
+printfn "After: %A" updatedCar2
+
+// Works for anonymous records too
+let alsoWorksForAnonymous (x:Car) = {| x with Interior.Seats = 7; Price = 99_999 |}
+let anonResult = alsoWorksForAnonymous myCar
+printfn "Anonymous: %A" anonResult
diff --git a/docs/fsharp/whats-new/snippets/fsharp-8/PropertyShorthand.fsx b/docs/fsharp/whats-new/snippets/fsharp-8/PropertyShorthand.fsx
new file mode 100644
index 0000000000000..c92af69b6bcbb
--- /dev/null
+++ b/docs/fsharp/whats-new/snippets/fsharp-8/PropertyShorthand.fsx
@@ -0,0 +1,32 @@
+// Property shorthand examples
+type Person = {Name : string; Age : int}
+let people = [ {Name = "Joe"; Age = 20} ; {Name = "Will"; Age = 30} ; {Name = "Joe"; Age = 51}]
+
+// Before F# 8
+let beforeThisFeature =
+ people
+ |> List.distinctBy (fun x -> x.Name)
+ |> List.groupBy (fun x -> x.Age)
+ |> List.map (fun (x,y) -> y)
+ |> List.map (fun x -> x.Head.Name)
+ |> List.sortBy (fun x -> x.ToString())
+
+printfn "Before: %A" beforeThisFeature
+
+// With F# 8
+let possibleNow =
+ people
+ |> List.distinctBy _.Name
+ |> List.groupBy _.Age
+ |> List.map snd
+ |> List.map _.Head.Name
+ |> List.sortBy _.ToString()
+
+printfn "After: %A" possibleNow
+
+// Standalone lambda functions
+let ageAccessor : Person -> int = _.Age
+let getNameLength = _.Name.Length
+
+printfn "Age accessor: %d" (ageAccessor people[0])
+printfn "Name length: %d" (getNameLength people[0])
diff --git a/docs/fsharp/whats-new/snippets/fsharp-8/StaticInInterfaces.fsx b/docs/fsharp/whats-new/snippets/fsharp-8/StaticInInterfaces.fsx
new file mode 100644
index 0000000000000..374a3386b24f5
--- /dev/null
+++ b/docs/fsharp/whats-new/snippets/fsharp-8/StaticInInterfaces.fsx
@@ -0,0 +1,14 @@
+// Static members in interfaces
+[]
+type IDemoable =
+ abstract member Show: string -> unit
+ static member AutoFormat(a) = sprintf "%A" a
+
+// Example implementation
+type MyType() =
+ interface IDemoable with
+ member _.Show(s) = printfn "Showing: %s" s
+
+let value = 42
+let formatted = IDemoable.AutoFormat(value)
+printfn "Formatted: %s" formatted
diff --git a/docs/fsharp/whats-new/snippets/fsharp-8/StaticLetInDU.fsx b/docs/fsharp/whats-new/snippets/fsharp-8/StaticLetInDU.fsx
new file mode 100644
index 0000000000000..7e8437a3ce961
--- /dev/null
+++ b/docs/fsharp/whats-new/snippets/fsharp-8/StaticLetInDU.fsx
@@ -0,0 +1,24 @@
+// Static let in discriminated unions
+open FSharp.Reflection
+
+type AbcDU = A | B | C
+ with
+ static let namesAndValues =
+ FSharpType.GetUnionCases(typeof)
+ |> Array.map (fun c -> c.Name, FSharpValue.MakeUnion (c,[||]) :?> AbcDU)
+ static let stringMap = namesAndValues |> dict
+ static let mutable cnt = 0
+
+ static do printfn "Init done! We have %i cases" stringMap.Count
+ static member TryParse text =
+ let cnt = System.Threading.Interlocked.Increment(&cnt)
+ stringMap.TryGetValue text, sprintf "Parsed %i" cnt
+
+// Test the functionality
+let result1 = AbcDU.TryParse "A"
+let result2 = AbcDU.TryParse "B"
+let result3 = AbcDU.TryParse "Invalid"
+
+printfn "Parse 'A': %A" result1
+printfn "Parse 'B': %A" result2
+printfn "Parse 'Invalid': %A" result3
diff --git a/docs/fsharp/whats-new/snippets/fsharp-8/StringInterpolation.fsx b/docs/fsharp/whats-new/snippets/fsharp-8/StringInterpolation.fsx
new file mode 100644
index 0000000000000..4b293571e0255
--- /dev/null
+++ b/docs/fsharp/whats-new/snippets/fsharp-8/StringInterpolation.fsx
@@ -0,0 +1,18 @@
+// Extended string interpolation syntax
+let classAttr = "item-panel"
+
+// Before F# 8 - braces must be doubled
+let cssOld = $""".{classAttr}:hover {{background-color: #eee;}}"""
+printfn "CSS Old: %s" cssOld
+
+// With F# 8 - multiple $ signs to define interpolation level
+let cssNew = $$""".{{classAttr}}:hover {background-color: #eee;}"""
+printfn "CSS New: %s" cssNew
+
+// HTML templating with triple $$$
+let templateNew = $$$"""
+
+"""
+printfn "Template: %s" templateNew
diff --git a/docs/fsharp/whats-new/snippets/fsharp-8/TailCallAttribute.fsx b/docs/fsharp/whats-new/snippets/fsharp-8/TailCallAttribute.fsx
new file mode 100644
index 0000000000000..b8462da09cb8b
--- /dev/null
+++ b/docs/fsharp/whats-new/snippets/fsharp-8/TailCallAttribute.fsx
@@ -0,0 +1,22 @@
+// TailCall attribute examples
+
+// This produces a warning - not tail recursive
+[]
+let rec factorialClassic n =
+ match n with
+ | 0u | 1u -> 1u
+ | _ -> n * (factorialClassic (n - 1u))
+
+// This is tail recursive - no warning
+[]
+let rec factorialWithAcc n accumulator =
+ match n with
+ | 0u | 1u -> accumulator
+ | _ -> factorialWithAcc (n - 1u) (n * accumulator)
+
+// Test both
+let result1 = factorialClassic 5u
+let result2 = factorialWithAcc 5u 1u
+
+printfn "Classic factorial(5): %d" result1
+printfn "Accumulator factorial(5): %d" result2
diff --git a/docs/fsharp/whats-new/snippets/fsharp-8/TryWithInCollections.fsx b/docs/fsharp/whats-new/snippets/fsharp-8/TryWithInCollections.fsx
new file mode 100644
index 0000000000000..78d853e5488a9
--- /dev/null
+++ b/docs/fsharp/whats-new/snippets/fsharp-8/TryWithInCollections.fsx
@@ -0,0 +1,13 @@
+// try-with within collection expressions
+let sum =
+ [ for x in [0;1] do
+ try
+ yield 1
+ yield (10/x)
+ yield 100
+ with _ ->
+ yield 1000 ]
+ |> List.sum
+
+printfn "Sum: %d" sum
+printfn "Expected: 1112 (list is [1;1000;1;10;100])"
From 19df83841a4471e30040a55a71d01e29313fbe1b Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 24 Nov 2025 12:23:46 +0000
Subject: [PATCH 3/4] Refactor code examples to use file includes instead of
inline code
Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com>
---
docs/fsharp/whats-new/fsharp-8.md | 147 +++---------------
.../snippets/fsharp-8/LiteralArithmetic.fsx | 6 +-
.../snippets/fsharp-8/NestedRecordUpdate.fsx | 12 +-
.../snippets/fsharp-8/PropertyShorthand.fsx | 12 +-
.../snippets/fsharp-8/StaticInInterfaces.fsx | 3 +-
.../snippets/fsharp-8/StaticLetInDU.fsx | 3 +-
.../snippets/fsharp-8/StringInterpolation.fsx | 10 +-
.../snippets/fsharp-8/TailCallAttribute.fsx | 8 +-
.../fsharp-8/TryWithInCollections.fsx | 3 +-
9 files changed, 60 insertions(+), 144 deletions(-)
diff --git a/docs/fsharp/whats-new/fsharp-8.md b/docs/fsharp/whats-new/fsharp-8.md
index 1f9aed227154b..5ce2fe1ca64ca 100644
--- a/docs/fsharp/whats-new/fsharp-8.md
+++ b/docs/fsharp/whats-new/fsharp-8.md
@@ -21,37 +21,17 @@ F# 8 introduces a succinct notation for simple lambda functions that access prop
**Before F# 8:**
-```fsharp
-type Person = {Name : string; Age : int}
-let people = [ {Name = "Joe"; Age = 20} ; {Name = "Will"; Age = 30} ; {Name = "Joe"; Age = 51}]
-
-let beforeThisFeature =
- people
- |> List.distinctBy (fun x -> x.Name)
- |> List.groupBy (fun x -> x.Age)
- |> List.map (fun (x,y) -> y)
- |> List.map (fun x -> x.Head.Name)
- |> List.sortBy (fun x -> x.ToString())
-```
+:::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:**
-```fsharp
-let possibleNow =
- people
- |> List.distinctBy _.Name
- |> List.groupBy _.Age
- |> List.map snd
- |> List.map _.Head.Name
- |> List.sortBy _.ToString()
-```
+:::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:
-```fsharp
-let ageAccessor : Person -> int = _.Age
-let getNameLength = _.Name.Length
-```
+:::code language="fsharp" source="snippets/fsharp-8/PropertyShorthand.fsx" id="Standalone":::
### Nested record field copy and update
@@ -59,30 +39,17 @@ F# 8 extends the copy-and-update syntax for nested records, eliminating the need
**Before F# 8:**
-```fsharp
-type SteeringWheel = { Type: string }
-type CarInterior = { Steering: SteeringWheel; Seats: int }
-type Car = { Interior: CarInterior; ExteriorColor: string option }
-
-let beforeThisFeature x =
- { x with Interior = { x.Interior with
- Steering = {x.Interior.Steering with Type = "yoke"}
- Seats = 5
- }
- }
-```
+:::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:**
-```fsharp
-let withTheFeature x = { x with Interior.Steering.Type = "yoke"; Interior.Seats = 5 }
-```
+:::code language="fsharp" source="snippets/fsharp-8/NestedRecordUpdate.fsx" id="After":::
This syntax also works for anonymous records:
-```fsharp
-let alsoWorksForAnonymous (x:Car) = {| x with Interior.Seats = 7; Price = 99_999 |}
-```
+:::code language="fsharp" source="snippets/fsharp-8/NestedRecordUpdate.fsx" id="Anonymous":::
### while!
@@ -127,26 +94,15 @@ F# 8 improves support for interpolated strings, taking inspiration from C# inter
**Before F# 8:**
-```fsharp
-let classAttr = "item-panel"
-let cssOld = $""".{classAttr}:hover {{background-color: #eee;}}"""
-```
+:::code language="fsharp" source="snippets/fsharp-8/StringInterpolation.fsx" id="Before":::
**With F# 8:**
-```fsharp
-let cssNew = $$""".{{classAttr}}:hover {background-color: #eee;}"""
-```
+:::code language="fsharp" source="snippets/fsharp-8/StringInterpolation.fsx" id="After":::
This is especially useful for HTML templating languages:
-```fsharp
-let templateNew = $$$"""
-
-"""
-```
+:::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/).
@@ -170,18 +126,16 @@ let renderedText = sprintf fullFormat 0.25 0.75
Numeric literals can now be expressed using existing operators and other literals. The compiler evaluates the expression at compile time.
-```fsharp
-let [] bytesInKB = 2f ** 10f
-let [] bytesInMB = bytesInKB * bytesInKB
-let [] bytesInGB = 1 <<< 30
-let [] customBitMask = 0b01010101uy
-let [] inverseBitMask = ~~~ customBitMask
-```
+:::code language="fsharp" source="snippets/fsharp-8/LiteralArithmetic.fsx" id="Literals":::
Supported operators:
- Numeric types: `+`, `-`, `*`, `/`, `%`, `&&&`, `|||`, `<<<`, `>>>`, `^^^`, `~~~`, `**`
- 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.
@@ -263,25 +217,9 @@ type Foo =
Interfaces can now declare and implement concrete static members (not to be confused with static abstract members from F# 7).
-**Before F# 8:**
-
-```fsharp
-[]
-type IDemoableOld =
- abstract member Show: string -> unit
-
-module IDemoableOld =
- let autoFormat(a) = sprintf "%A" a
-```
-
**With F# 8:**
-```fsharp
-[]
-type IDemoable =
- abstract member Show: string -> unit
- static member AutoFormat(a) = sprintf "%A" a
-```
+:::code language="fsharp" source="snippets/fsharp-8/StaticInInterfaces.fsx" id="Interface":::
### Static let in discriminated unions, records, structs, and types without primary constructors
@@ -292,38 +230,13 @@ F# 8 enables `static let`, `static let mutable`, `static do`, and `static member
- Structs (including `[]` unions and records)
- Types without primary constructors
-```fsharp
-open FSharp.Reflection
-
-type AbcDU = A | B | C
- with
- static let namesAndValues =
- FSharpType.GetUnionCases(typeof)
- |> Array.map (fun c -> c.Name, FSharpValue.MakeUnion (c,[||]) :?> AbcDU)
- static let stringMap = namesAndValues |> dict
- static let mutable cnt = 0
-
- static do printfn "Init done! We have %i cases" stringMap.Count
- static member TryParse text =
- let cnt = System.Threading.Interlocked.Increment(&cnt)
- stringMap.TryGetValue text, sprintf "Parsed %i" cnt
-```
+:::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.
-```fsharp
-let sum =
- [ for x in [0;1] do
- try
- yield 1
- yield (10/x)
- yield 100
- with _ ->
- yield 1000 ]
- |> List.sum
-```
+:::code language="fsharp" source="snippets/fsharp-8/TryWithInCollections.fsx" id="TryWith":::
This yields the list `[1;1000;1;10;100]`, which sums to 1112.
@@ -335,21 +248,9 @@ F# 8 adds many new and improved diagnostic messages.
Use the `[]` attribute to explicitly state your intention of defining a tail-recursive function. The compiler warns you if your function makes non-tail-recursive calls.
-```fsharp
-[]
-let rec factorialClassic n =
- match n with
- | 0u | 1u -> 1u
- | _ -> n * (factorialClassic (n - 1u))
-// This produces a warning
-
-[]
-let rec factorialWithAcc n accumulator =
- match n with
- | 0u | 1u -> accumulator
- | _ -> factorialWithAcc (n - 1u) (n * accumulator)
-// This is a tail call and does NOT produce a warning
-```
+:::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
diff --git a/docs/fsharp/whats-new/snippets/fsharp-8/LiteralArithmetic.fsx b/docs/fsharp/whats-new/snippets/fsharp-8/LiteralArithmetic.fsx
index c75fd64713b38..945cbb126eda6 100644
--- a/docs/fsharp/whats-new/snippets/fsharp-8/LiteralArithmetic.fsx
+++ b/docs/fsharp/whats-new/snippets/fsharp-8/LiteralArithmetic.fsx
@@ -1,9 +1,10 @@
-// Arithmetic operators in literals
+//
let [] bytesInKB = 2f ** 10f
let [] bytesInMB = bytesInKB * bytesInKB
let [] bytesInGB = 1 <<< 30
let [] customBitMask = 0b01010101uy
let [] inverseBitMask = ~~~ customBitMask
+//
printfn "Bytes in KB: %f" bytesInKB
printfn "Bytes in MB: %f" bytesInMB
@@ -11,11 +12,12 @@ printfn "Bytes in GB: %d" bytesInGB
printfn "Custom bit mask: %d" customBitMask
printfn "Inverse bit mask: %d" inverseBitMask
-// Works for enum values
+//
type MyEnum =
| A = (1 <<< 5)
| B = (17 * 45 % 13)
| C = bytesInGB
+//
printfn "MyEnum.A: %d" (int MyEnum.A)
printfn "MyEnum.B: %d" (int MyEnum.B)
diff --git a/docs/fsharp/whats-new/snippets/fsharp-8/NestedRecordUpdate.fsx b/docs/fsharp/whats-new/snippets/fsharp-8/NestedRecordUpdate.fsx
index cd001df798ddd..8cb66ef27ade9 100644
--- a/docs/fsharp/whats-new/snippets/fsharp-8/NestedRecordUpdate.fsx
+++ b/docs/fsharp/whats-new/snippets/fsharp-8/NestedRecordUpdate.fsx
@@ -1,7 +1,8 @@
-// Nested record field copy and update
+//
type SteeringWheel = { Type: string }
type CarInterior = { Steering: SteeringWheel; Seats: int }
type Car = { Interior: CarInterior; ExteriorColor: string option }
+//
let myCar = {
Interior = {
@@ -11,16 +12,18 @@ let myCar = {
ExteriorColor = Some "red"
}
-// Before F# 8
+//
let beforeThisFeature x =
{ x with Interior = { x.Interior with
Steering = {x.Interior.Steering with Type = "yoke"}
Seats = 5
}
}
+//
-// With F# 8
+//
let withTheFeature x = { x with Interior.Steering.Type = "yoke"; Interior.Seats = 5 }
+//
let updatedCar1 = beforeThisFeature myCar
let updatedCar2 = withTheFeature myCar
@@ -28,7 +31,8 @@ let updatedCar2 = withTheFeature myCar
printfn "Before: %A" updatedCar1
printfn "After: %A" updatedCar2
-// Works for anonymous records too
+//
let alsoWorksForAnonymous (x:Car) = {| x with Interior.Seats = 7; Price = 99_999 |}
+//
let anonResult = alsoWorksForAnonymous myCar
printfn "Anonymous: %A" anonResult
diff --git a/docs/fsharp/whats-new/snippets/fsharp-8/PropertyShorthand.fsx b/docs/fsharp/whats-new/snippets/fsharp-8/PropertyShorthand.fsx
index c92af69b6bcbb..cf033c2b50ccc 100644
--- a/docs/fsharp/whats-new/snippets/fsharp-8/PropertyShorthand.fsx
+++ b/docs/fsharp/whats-new/snippets/fsharp-8/PropertyShorthand.fsx
@@ -1,8 +1,9 @@
-// Property shorthand examples
+//
type Person = {Name : string; Age : int}
let people = [ {Name = "Joe"; Age = 20} ; {Name = "Will"; Age = 30} ; {Name = "Joe"; Age = 51}]
+//
-// Before F# 8
+//
let beforeThisFeature =
people
|> List.distinctBy (fun x -> x.Name)
@@ -10,10 +11,11 @@ let beforeThisFeature =
|> List.map (fun (x,y) -> y)
|> List.map (fun x -> x.Head.Name)
|> List.sortBy (fun x -> x.ToString())
+//
printfn "Before: %A" beforeThisFeature
-// With F# 8
+//
let possibleNow =
people
|> List.distinctBy _.Name
@@ -21,12 +23,14 @@ let possibleNow =
|> List.map snd
|> List.map _.Head.Name
|> List.sortBy _.ToString()
+//
printfn "After: %A" possibleNow
-// Standalone lambda functions
+//
let ageAccessor : Person -> int = _.Age
let getNameLength = _.Name.Length
+//
printfn "Age accessor: %d" (ageAccessor people[0])
printfn "Name length: %d" (getNameLength people[0])
diff --git a/docs/fsharp/whats-new/snippets/fsharp-8/StaticInInterfaces.fsx b/docs/fsharp/whats-new/snippets/fsharp-8/StaticInInterfaces.fsx
index 374a3386b24f5..04f95276ad299 100644
--- a/docs/fsharp/whats-new/snippets/fsharp-8/StaticInInterfaces.fsx
+++ b/docs/fsharp/whats-new/snippets/fsharp-8/StaticInInterfaces.fsx
@@ -1,8 +1,9 @@
-// Static members in interfaces
+//
[]
type IDemoable =
abstract member Show: string -> unit
static member AutoFormat(a) = sprintf "%A" a
+//
// Example implementation
type MyType() =
diff --git a/docs/fsharp/whats-new/snippets/fsharp-8/StaticLetInDU.fsx b/docs/fsharp/whats-new/snippets/fsharp-8/StaticLetInDU.fsx
index 7e8437a3ce961..6e3c82c8c9be6 100644
--- a/docs/fsharp/whats-new/snippets/fsharp-8/StaticLetInDU.fsx
+++ b/docs/fsharp/whats-new/snippets/fsharp-8/StaticLetInDU.fsx
@@ -1,6 +1,6 @@
-// Static let in discriminated unions
open FSharp.Reflection
+//
type AbcDU = A | B | C
with
static let namesAndValues =
@@ -13,6 +13,7 @@ type AbcDU = A | B | C
static member TryParse text =
let cnt = System.Threading.Interlocked.Increment(&cnt)
stringMap.TryGetValue text, sprintf "Parsed %i" cnt
+//
// Test the functionality
let result1 = AbcDU.TryParse "A"
diff --git a/docs/fsharp/whats-new/snippets/fsharp-8/StringInterpolation.fsx b/docs/fsharp/whats-new/snippets/fsharp-8/StringInterpolation.fsx
index 4b293571e0255..0b1371b1bc48b 100644
--- a/docs/fsharp/whats-new/snippets/fsharp-8/StringInterpolation.fsx
+++ b/docs/fsharp/whats-new/snippets/fsharp-8/StringInterpolation.fsx
@@ -1,18 +1,20 @@
-// Extended string interpolation syntax
let classAttr = "item-panel"
-// Before F# 8 - braces must be doubled
+//
let cssOld = $""".{classAttr}:hover {{background-color: #eee;}}"""
+//
printfn "CSS Old: %s" cssOld
-// With F# 8 - multiple $ signs to define interpolation level
+//
let cssNew = $$""".{{classAttr}}:hover {background-color: #eee;}"""
+//
printfn "CSS New: %s" cssNew
-// HTML templating with triple $$$
+//
let templateNew = $$$"""
"""
+//
printfn "Template: %s" templateNew
diff --git a/docs/fsharp/whats-new/snippets/fsharp-8/TailCallAttribute.fsx b/docs/fsharp/whats-new/snippets/fsharp-8/TailCallAttribute.fsx
index b8462da09cb8b..e9af7fe3ad1b5 100644
--- a/docs/fsharp/whats-new/snippets/fsharp-8/TailCallAttribute.fsx
+++ b/docs/fsharp/whats-new/snippets/fsharp-8/TailCallAttribute.fsx
@@ -1,18 +1,18 @@
-// TailCall attribute examples
-
-// This produces a warning - not tail recursive
+//
[]
let rec factorialClassic n =
match n with
| 0u | 1u -> 1u
| _ -> n * (factorialClassic (n - 1u))
+//
-// This is tail recursive - no warning
+//
[]
let rec factorialWithAcc n accumulator =
match n with
| 0u | 1u -> accumulator
| _ -> factorialWithAcc (n - 1u) (n * accumulator)
+//
// Test both
let result1 = factorialClassic 5u
diff --git a/docs/fsharp/whats-new/snippets/fsharp-8/TryWithInCollections.fsx b/docs/fsharp/whats-new/snippets/fsharp-8/TryWithInCollections.fsx
index 78d853e5488a9..2b7ba28cddfb2 100644
--- a/docs/fsharp/whats-new/snippets/fsharp-8/TryWithInCollections.fsx
+++ b/docs/fsharp/whats-new/snippets/fsharp-8/TryWithInCollections.fsx
@@ -1,4 +1,4 @@
-// try-with within collection expressions
+//
let sum =
[ for x in [0;1] do
try
@@ -8,6 +8,7 @@ let sum =
with _ ->
yield 1000 ]
|> List.sum
+//
printfn "Sum: %d" sum
printfn "Expected: 1112 (list is [1;1000;1;10;100])"
From 97b59a9a12242b47c147a7bf95af16771af54abd Mon Sep 17 00:00:00 2001
From: Adam Boniecki <20281641+abonie@users.noreply.github.com>
Date: Fri, 28 Nov 2025 21:31:41 +0100
Subject: [PATCH 4/4] Apply suggestion from @abonie
---
docs/fsharp/whats-new/fsharp-8.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/docs/fsharp/whats-new/fsharp-8.md b/docs/fsharp/whats-new/fsharp-8.md
index 5ce2fe1ca64ca..1f0c6345b9f89 100644
--- a/docs/fsharp/whats-new/fsharp-8.md
+++ b/docs/fsharp/whats-new/fsharp-8.md
@@ -129,6 +129,7 @@ Numeric literals can now be expressed using existing operators and other literal
:::code language="fsharp" source="snippets/fsharp-8/LiteralArithmetic.fsx" id="Literals":::
Supported operators:
+
- Numeric types: `+`, `-`, `*`, `/`, `%`, `&&&`, `|||`, `<<<`, `>>>`, `^^^`, `~~~`, `**`
- Booleans: `not`, `&&`, `||`