diff --git a/release-notes/11.0/preview/rc2/README.md b/release-notes/11.0/preview/rc2/README.md index f17bf30e79..e8051f51b8 100644 --- a/release-notes/11.0/preview/rc2/README.md +++ b/release-notes/11.0/preview/rc2/README.md @@ -11,7 +11,7 @@ Browse the .NET 11 RC 2 release notes by component: ## Languages - C# -- F# +- [F#](./fsharp.md) ## Workloads, Libraries, & More diff --git a/release-notes/11.0/preview/rc2/fsharp.md b/release-notes/11.0/preview/rc2/fsharp.md new file mode 100644 index 0000000000..4504dc2edc --- /dev/null +++ b/release-notes/11.0/preview/rc2/fsharp.md @@ -0,0 +1,48 @@ +# F# in .NET 11 RC 2 - Release Notes + +- [Bound concurrent asynchronous work](#bound-concurrent-asynchronous-work) +- [Require named arguments for selected APIs](#require-named-arguments-for-selected-apis) +- [Reraise from a computation expression handler](#reraise-from-a-computation-expression-handler) + +## Bound concurrent asynchronous work + +`Async.parallelLimit` runs a sequence of async computations with a specified maximum number in flight and returns their results in input order. This helps control how much work a batch starts at once without writing a concurrency limiter yourself. Related helpers include `Async.parallelDoLimit`, `Task.parallelLimit`, and sequential variants ([dotnet/fsharp #20294](https://github.com/dotnet/fsharp/pull/20294)). + +```fsharp +let results = + [ for i in 1..5 -> async { return i * i } ] + |> Async.parallelLimit 2 + |> Async.RunSynchronously +// [| 1; 4; 9; 16; 25 |] +``` + +## Require named arguments for selected APIs + +With `preview`, F# recognizes `System.Diagnostics.CodeAnalysis.RequireNamedArgumentsAttribute` on a method or constructor and reports `FS3923` when a caller passes the required arguments positionally. This lets API authors make call sites more explicit ([dotnet/fsharp #20340](https://github.com/dotnet/fsharp/pull/20340)). + +The RC 2 BCL does not yet include the attribute. Define the attribute in your project or use a library that provides it before marking a member: + +```fsharp +open System.Diagnostics.CodeAnalysis + +type Calculator() = + [] + member _.Double(value: int) = value * 2 + +let answer = Calculator().Double(value = 21) // 42 +``` + +The [maintained fixture](../../samples/fsharp/README.md) includes the attribute definition and verifies the named call against the RC 2 SDK. + +## Reraise from a computation expression handler + +With the preview language version, `reraise ()` works inside the `with` handler of an `async`, `task`, or custom computation expression. The handler propagates the original exception with its stack trace, avoiding the loss of origin information that can result from `raise e` ([dotnet/fsharp #20405](https://github.com/dotnet/fsharp/pull/20405)). + +```fsharp +let operation = async { + try + failwith "operation failed" + with _ -> + reraise () +} +``` diff --git a/release-notes/11.0/samples/fsharp/FSharp.fsproj b/release-notes/11.0/samples/fsharp/FSharp.fsproj new file mode 100644 index 0000000000..af92b4e4b0 --- /dev/null +++ b/release-notes/11.0/samples/fsharp/FSharp.fsproj @@ -0,0 +1,11 @@ + + + Exe + net11.0 + preview + + + + + + diff --git a/release-notes/11.0/samples/fsharp/Polyfill.fs b/release-notes/11.0/samples/fsharp/Polyfill.fs new file mode 100644 index 0000000000..aebeeddc44 --- /dev/null +++ b/release-notes/11.0/samples/fsharp/Polyfill.fs @@ -0,0 +1,7 @@ +namespace System.Diagnostics.CodeAnalysis + +open System + +[] +type RequireNamedArgumentsAttribute() = + inherit Attribute() diff --git a/release-notes/11.0/samples/fsharp/Program.fs b/release-notes/11.0/samples/fsharp/Program.fs new file mode 100644 index 0000000000..f9ef6d372f --- /dev/null +++ b/release-notes/11.0/samples/fsharp/Program.fs @@ -0,0 +1,50 @@ +open System +open System.Diagnostics.CodeAnalysis +open System.Threading +open System.Runtime.CompilerServices + +let mutable active = 0 +let mutable peak = 0 +let gate = obj() +let computations = + [ for i in 1..5 -> + async { + let running = Interlocked.Increment &active + lock gate (fun () -> peak <- max peak running) + do! Async.Sleep 30 + Interlocked.Decrement &active |> ignore + return i * i + } ] + +let results = computations |> Async.parallelLimit 2 |> Async.RunSynchronously +if results <> [| 1; 4; 9; 16; 25 |] || peak > 2 || peak < 1 then + failwithf "Unexpected bounded-parallel results: %A; peak concurrency: %d" results peak +printfn "Bounded-parallel work passed (peak concurrency: %d)." peak + +type Calculator() = + [] + member _.Double(value: int) = value * 2 + +let actual = Calculator().Double(value = 21) +if actual <> 42 then failwithf "Expected 42, got %d" actual +printfn "Named argument validation passed." + +let original = InvalidOperationException("original exception") + +[] +let throwOriginal () = raise original + +let operation = async { + try + throwOriginal () + with _ -> + reraise () +} + +try + operation |> Async.RunSynchronously + failwith "Expected the original exception." +with ex when obj.ReferenceEquals(ex, original) -> + if not (ex.StackTrace.Contains("throwOriginal")) then + failwith "The original exception stack trace was lost." + printfn "Computation-expression reraise retained the exception and stack trace." diff --git a/release-notes/11.0/samples/fsharp/README.md b/release-notes/11.0/samples/fsharp/README.md new file mode 100644 index 0000000000..8984f38106 --- /dev/null +++ b/release-notes/11.0/samples/fsharp/README.md @@ -0,0 +1,3 @@ +# F# release-note validation + +With SDK `11.0.100-rc.2.26475.137` on `PATH`, run `.\Validate.ps1`. The fixture checks the ordered results and peak concurrency of `Async.parallelLimit 2`, and confirms that `reraise ()` in an async computation-expression handler preserves the original exception and stack trace. It also supplies `RequireNamedArgumentsAttribute` as a polyfill because this build does not ship it in the BCL, runs a marked method using a named argument, and checks that a positional call in the separate `invalid/Invalid.fsproj` reports `FS3923`. diff --git a/release-notes/11.0/samples/fsharp/Validate.ps1 b/release-notes/11.0/samples/fsharp/Validate.ps1 new file mode 100644 index 0000000000..d43ecd519b --- /dev/null +++ b/release-notes/11.0/samples/fsharp/Validate.ps1 @@ -0,0 +1,11 @@ +$ErrorActionPreference = 'Stop' +$dotnet = (Get-Command dotnet -ErrorAction Stop).Source + +& $dotnet run --project (Join-Path $PSScriptRoot 'FSharp.fsproj') +if ($LASTEXITCODE -ne 0) { throw 'The named-argument example did not run.' } + +$diagnostics = & $dotnet build (Join-Path $PSScriptRoot 'invalid\Invalid.fsproj') --nologo 2>&1 | Out-String +if ($LASTEXITCODE -eq 0 -or $diagnostics -notmatch 'error FS3923') { + throw "Expected positional-call error FS3923, got: $diagnostics" +} +Write-Output 'Positional call correctly reported FS3923.' diff --git a/release-notes/11.0/samples/fsharp/invalid/Invalid.fs b/release-notes/11.0/samples/fsharp/invalid/Invalid.fs new file mode 100644 index 0000000000..0c70ff88c9 --- /dev/null +++ b/release-notes/11.0/samples/fsharp/invalid/Invalid.fs @@ -0,0 +1,7 @@ +open System.Diagnostics.CodeAnalysis + +type Calculator() = + [] + member _.Double(value: int) = value * 2 + +Calculator().Double(21) |> ignore diff --git a/release-notes/11.0/samples/fsharp/invalid/Invalid.fsproj b/release-notes/11.0/samples/fsharp/invalid/Invalid.fsproj new file mode 100644 index 0000000000..885fdfcf29 --- /dev/null +++ b/release-notes/11.0/samples/fsharp/invalid/Invalid.fsproj @@ -0,0 +1,11 @@ + + + Exe + net11.0 + preview + + + + + +