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 = $$$""" +
+

{{title}}

+
+""" +``` + +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 = $$$""" +
+

{{title}}

+
+""" +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 = $$$""" -
-

{{title}}

-
-""" -``` +:::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 $$$ +// 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`, `&&`, `||`