Kommentar
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Välkommen till F# 11! Den här versionen hjälper dig att hålla domänmodeller åtskilda, återanvända generiska algoritmer, samordna asynkrona arbetsuppgifter och minska kompilatorns minnesanvändning.
- Med record spreads kan fält återanvändas utan att tvinga lagrings- och applikationsmodeller att dela en typ.
- Utökade medlemmar i generiska algoritmer gör att befintliga typer kan få tillgång till samma operationer utan omslutande klasser.
- .NET-integration bevarar delegeringsmetadata och återanvänder API-dokumentation.
- Hjälpmetoder för asynkrona aktiviteter och uppgifter vidarebefordrar avbrott över API-gränser och begränsar samtidiga arbetsuppgifter.
- Kompilator- och biblioteksförbättringar minskar upprepad analys och undviker vissa allokeringar.
Tack till F#-gemenskapen för förslagen, implementationerna, granskningarna och testerna som ligger bakom dessa ändringar. Länkar till medverkande hör till funktionerna nedan.
Note
Exemplen kräver en F# 11-kompilator och FSharp.Core 11 med de funktioner som visas. I .NET 11.0.100 SDK:n kräver F# 11.2-funktioner <LangVersion>preview</LangVersion>; se språkinställningar och planerat SDK-stöd. Exemplet runtime-async har ytterligare krav för körning; en tidigare .NET 11 SDK i sig kanske inte innehåller alla ändringar.
Postspridningar: separata modeller utan upprepade mappningar
För att hålla lagrings- och applikationsmodeller åtskilda återanvänder spreadar av poster sina gemensamma fält utan upprepade deklarationer. Här delar modellerna kundinformation men använder olika identifierartyper:
open System
type CustomerId = CustomerId of Guid
type CustomerDetails = { Name: string; PreferredLanguage: string }
type CustomerRow =
{ ...CustomerDetails
Id: string
InternalNote: string }
type Customer =
{ ...CustomerDetails
Id: CustomerId }
...CustomerDetails bidrar med fältdeklarationer. Vid gränsen ...row kopieras värden medan konverteringen parsar identifieraren:
let toCustomer (row: CustomerRow) =
match Guid.TryParse row.Id with
| true, id -> Ok ({ ...row; Id = CustomerId id }: Customer)
| false, _ -> Error "Invalid customer identifier."
Målet behåller de delade uppgifterna, ersätter Id och utesluter InternalNote.
Guid.TryParse, inte spread, validerar identifieraren; felaktiga indata genererar Error.
Detta hanterar mappningsproblemet på applikationslagret. Värdespridningar kombinerar också namngivna och anonyma poster; typspridningar skapar endast namngivna poster. Senare fält vinner och källor körs en gång i den angivna ordningen. Se Record spread operators för kompositionsexempel och risker med delad utveckling.
Tack till @brianrourkeboll för rekordspreadar.
Tilläggsmedlemmar för generiska algoritmer, operatorer och tupplar
Om du vill dela en algoritm mellan befintliga typer låter F# 11.2 tilläggsmedlemmar uppfylla statiskt lösta villkor för typparametrar (SRTP). En transformering kan till exempel hantera listor och alternativ utan omslutningstyper.
Note
I .NET 11.0.100 SDK aktiverar du <LangVersion>preview</LangVersion> där tillägg identifieras och öppna deras modul. Se krav för bibliotek.
Dela en transformation mellan listor och alternativ
Med bibliotekets map tillgängligt kan programmet rensa endera representationen, ändra elementtyper och skapa transformeringar:
let inline cleanNames values =
map (fun (name: string) -> name.Trim().ToUpperInvariant()) values
let batch: string list = cleanNames [ " Ada "; " grace " ]
let optional: string option = cleanNames (Some " linus ")
let missing: string option = cleanNames (None: string option)
let lengths: int list = map String.length batch
let nested: int option list = map (map String.length) [ Some "abc"; None ]
printfn "%A; %A; %A" batch optional missing
printfn "%A; %A" lengths nested
// ["ADA"; "GRACE"]; Some "LINUS"; None
// [3; 5]; [Some 3; None]
Adaptrarna i Structural.Adapters använder välbekanta åtgärder i stället för att införa nya containrar:
type List<'T> with
static member Map(source: 'T list, mapping: 'T -> 'U) : 'U list =
List.map mapping source
type Option<'T> with
static member Map(source: 'T option, mapping: 'T -> 'U) : 'U option =
Option.map mapping source
Den delade funktionen i Structural.Algorithms kräver att indatatypen har en statisk Map-medlem:
let inline map (mapping: 'T -> 'U) (source: ^Input) : ^Output =
(^Input :
(static member Map: ^Input * ('T -> 'U) -> ^Output)
(source, mapping))
^Input och ^Output är statiskt lösta typer. Vid varje konkret anrop väljer kompilatorn medlemmen med den nödvändiga signaturen. Tilläggen lägger inte till någon gränssnittsimplementering. Det hela programmet öppnar båda modulerna.
Operatorer, kontext och utdatatyper
Samma mekanism stöder tupppeloperatorer och kontextspridning och utdatariktad konstruktion. Inbyggda tupelhuvuden behöver språkfunktionen F# 11.2 aktiverad i sina deklarationer samt på anropsplatser för utökad upplösning.
Ett separat tillägg, AllowOverloadOnReturnType, låter en fabriksmetod deklarera överlagrade metoder som skiljs åt genom sina returtyper:
type Inject =
[<AllowOverloadOnReturnType>]
static member Pure(value: 'T) : 'T list = [value]
[<AllowOverloadOnReturnType>]
static member Pure(value: 'T) : 'T option = Some value
let batch: string list = Inject.Pure "ready"
let optional: string option = Inject.Pure "ready"
// ["ready"] and Some "ready"
Det här Core 11-attributet kräver inte förhandsvisning. Resultatannoteringen väljer en överlagrad funktion; utan den förblir anropet tvetydigt. Se Välj efter returtyp.
Tack till @gusty för att ha utökat stödet för generisk programmering.
.NET-integration och utvecklarupplevelsen
Direkta delegater bevarar hanterarens verkliga gränssnitt
För att exponera hanterarnamn och bindningsattribut för ramverk kan F# 11 rikta sig direkt mot en kompatibel namngiven funktion i stället för en omslutande funktion. ASP.NET Core Minimala API:er kan sedan läsa väg-, tjänst- och huvudkontraktet:
open System
open Microsoft.AspNetCore.Builder
open Microsoft.AspNetCore.Mvc
open Microsoft.Extensions.DependencyInjection
type ShipmentStore() =
member _.Status trackingId =
if trackingId = "SHP-1042" then "Dispatched" else "Unknown"
let status
(trackingId: string)
([<FromServices>] store: ShipmentStore)
([<FromHeader(Name = "X-Customer")>] customer: string) =
$"{customer}: {trackingId} is {store.Status trackingId}"
let handler = Func<string, ShipmentStore, string, string>(status)
Se det körbara projektet och regler för konstruktion/kompatibilitet. Variabelinfångning och argumentanpassning kan fortfarande kräva wrapper-funktioner.
Tack till @kerams för direkta delegater.
Återanvänd XML-dokumentation
Om du vill hantera delade API-kommentarer på ett ställe, placerar du dem i shared.xml:
<docs>
<tracking>
<remarks>An unrecognized tracking identifier has status Unknown.</remarks>
</tracking>
</docs>
Med GenerateDocumentationFile aktiverat expanderar F# 11 <include> till de emitterade medlemsposterna:
/// <summary>Find the latest status of a shipment.</summary>
/// <include file="shared.xml" path="/docs/tracking/*"/>
let status trackingId =
if trackingId = "SHP-1042" then "Dispatched" else "Unknown"
/// <summary>Find the latest statuses of several shipments.</summary>
/// <include file="shared.xml" path="/docs/tracking/*"/>
let statuses trackingIds =
trackingIds |> List.map (fun id -> id, status id)
Implementeringen av include-funktionen uppdaterar båda beskrivningarna utifrån ett fragment. Separat tillhandahåller verktygslösaren grundläggande beskrivningar för <inheritdoc/>; kompilatorn låter den taggen vara kvar för dem som konsumerar dokumentationen. I XML-dokumentationen finns exempel och matchningsregler.
Tack till @T-Gro för delad XML-dokumentation i FSharp.Core.
Felsöka inline-funktioner i icke-optimerade byggen
Om du vill granska argument till inline-hjälpfunktioner sätter du en brytpunkt på let total i en icke-optimerad version och stegar in i addFee. F# 11 föredrar nu anropsbara metoder i dessa byggen:
let inline addFee (fee: decimal) (amount: decimal) =
let total = amount + fee
total
let invoiceTotal = addFee 2m 40m
printfn "%M" invoiceTotal
// 42
Optimerade versioner behåller inlining. Vissa konstruktioner måste fortfarande expanderas. Se Felsök infogade funktioner för information om dessa begränsningar och kontrollen --always-inline.
Asynkronisering och uppgiftssamordning
För att ansluta F#-arbetsflöden till uppgiftsbaserade API:er lägger FSharp.Core 11 till annulleringsmedvetna bryggor, avgränsade batchar och resultattransformeringar.
Bind utestående I/O, inte bara resultatsamlingen
En stor mängd läsoperationer kan starta mer arbete än vad ett program bör ha utestående. För att avgöra när aktivitetsbaserat arbete börjar behöver en koordinator faktorer, inte uppgifter som redan har startats:
open System.IO
open System.Threading
let readFiles (paths: seq<string>) (ct: CancellationToken) =
paths
|> Seq.map (fun path ->
fun token -> File.ReadAllTextAsync(path, token))
|> Task.parallelLimit 4 ct
Högst fyra läsoperationer körs samtidigt; Resultaten behåller indataordningen. Ett misslyckande begär syskonavbokning via de angivna token. Detta begränsar samtidighet, inte begäranden per sekund. Se Async- och uppgiftsfunktioner för sekventiella varianter och varianter med enhetsresultat.
Behåll annullering och det underliggande undantaget över gränsen
När ett arbetsflöde startar externt arbete bör dess token följa med det arbetet.
Async.StartTaskImmediate skickar det aktiva arbetsflödets token till uppgiftsfabriken och väntar på slutförande:
open System.IO
let tryReadFile path =
async {
try
let! contents =
Async.StartTaskImmediate(fun ct -> File.ReadAllTextAsync(path, ct))
return Some contents
with :? FileNotFoundException ->
return None
}
Ett enskilt aktivitetsfel når den här hanteraren i form av den underliggande FileNotFoundException. För en uppgift eller värdeuppgift som redan har skapats använder du Async.Await; den äldre Async.AwaitTask behåller sitt undantagshanteringsbeteende.
För uppgiftsbaserad anropande kod tillhandahåller Task.startAsyncImmediate token workflow den omvända bryggan. Den startar på den anropande tråden tills det första asynkrona avbrottet.
Använd matchande hjälpverktyg för korta omvandlingar
Om du vill transformera ett resultat utan ett fullständigt beräkningsuttryck använder du matchande hjälpverktyg i Microsoft.FSharp.Control. Du kan till exempel läsa en fil, hämta dess längd och returnera ett vanligt misslyckande som Error:
open System.IO
open System.Threading
let fileLength (path: string) (ct: CancellationToken) =
File.ReadAllTextAsync(path, ct)
|> Task.map String.length
|> Task.catch
En fil som innehåller Report 1 ger Ok 8; en fil som saknas ger en Error innehållande FileNotFoundException. Avbrytningen fortplantas fortfarande.
Async och ValueTask erbjuder matchande map, bind och felhjälpare; se Async och uppgiftsfunktioner. Använd ett beräkningsuttryck för förgreningar eller flera beroende värden.
ValueTask-hjälparna kräver Cores netstandard2.1 eller net10.0 build och engångsanvändning.
Tack vare @bartelink för bidrag till async/task-API.
Kasta om efter asynkron felrapportering
Driftloggning bör lägga till information utan att ändra vad anroparen fångar upp eller dölja var felet började. Beräkningsuttryckshanterare kan nu använda reraise () även efter att de har awaitat loggaren:
Note
Beräkningsuttrycksåterkastning är en F# 11.2-funktion. I .NET 11.0.100 SDK aktiverar du <LangVersion>preview</LangVersion>.
open System
open System.IO
open System.Threading.Tasks
let readReport (logFailure: exn -> Task) path =
task {
try
return! File.ReadAllTextAsync(path)
with :? IOException as error ->
do! logFailure error
return reraise ()
}
Efter lyckad loggning vidarebefordras samma undantag och ursprunglig information om throw-site. Ett loggfel kan ersätta originalet. Detta åtgärdar begäran om asynkron observerbarhet utan asynkronitet vid körning. Se Vidarebefordra undantag från ett beräkningsuttryck.
Tack vare @xperiandri för detta stöd.
FSharp.Core kan byggas på moderna .NET
För att använda moderna API:er utan att behöva släppa stöd för äldre målplattformar lägger FSharp.Core till net10.0 tillsammans med netstandard2.0 och netstandard2.1. NuGet väljer det kompatibla bygget. Uppdaterad verktygspaketering möjliggör också uppgiftsbaserad asynkron disponering i F# Interactive, som i kompilerade projekt. Se FSharp.Core-målplattformar.
Tack till @T-Gro för modern Core-targeting och paketering av SDK-verktyg.
Runtime async för byggarskapare
Författare av builders kan låta körmiljön pausa och återuppta markerade metoder i stället för att använda kompilatorgenererade tillståndsmaskiner. En experimentell biblioteksdefinierad byggare exponerar mekanismen som ett beräkningsuttryck:
let readManifest (path: string) =
runtimeTask {
let! text = System.IO.File.ReadAllTextAsync path
return text.Length
}
Note
Den här experimentella byggaren använder en F# 11.2-funktion där dess infogade implementering definieras eller expanderas. I .NET 11.0.100 SDK aktiverar du förhandsversionen. Det kräver också Core 11:s net10.0 build och referenser samt en runtime med stöd för runtime-async. Den verifierade körnings-/referenskombinationen här är .NET 11 RC1. Att rikta in sig på .NET 10 räcker inte. Se asynkrona Runtime-experiment.
FSharp.Core levererar inte det här exemplet som en allmän builder. Vanlig task kod och async kod växlar inte automatiskt till den, och anropare av ett redan kompilerat vanligt API som returnerar Task behöver inte förhandsläge.
Tack till @majocha för runtime-async.
Native AOT-vänlig text och tvådimensionella matriser
För att stödja Native AOT tar F# 11 bort kraven på reflektionsbaserad formatering och dynamisk allokering från de vanligaste text- och matrisoperationerna.
Skriv ut värden och skapa strängar utan ett redundant formatlager
När du redan har en sträng behöver du inte ett "%s"-format bara för att skriva den. Nya kärnfunktioner print och printn accepterar värden via F#:s string konvertering; printn lägger till en ny rad:
let report (processed: int) (total: decimal) (elapsed: TimeSpan) : string =
$"{processed} items, total {total:F2}, elapsed {elapsed.TotalMilliseconds:F0} ms"
printn (report 12 145.50m (TimeSpan.FromMilliseconds 250.0))
// 12 items, total 145.50, elapsed 250 ms
Stränginterpolation använder direktkonvertering och String.Concat i fler fall. .NET-formathål som {total:F2} använder invariant format och boxar värdet; %A och printf-format med precision kan fortfarande behöva printf-formatering. Välj en angiven kultur för lokaliserad utdata.
Med --reflectionfree får poster och unioner också genererade ToString utdata. Se Interpolation och Native AOT för kulturexempel och sökvägar som stöds.
Tack till @bbatsov för utskriftsfunktionerna och till @charlesroddie för interpoleringsimplementeringen.
Behåll vanliga nollbaserade rutnätsåtgärder
Ett nollbaserat rutnät bör inte kräva en ersättnings-Array2D-modul bara för att undvika en allokeringssökväg för dynamisk kod. Uppdaterade Core-implementeringar stöder välkänd initialisering och omvandling:
let grid = Array2D.init 3 4 (fun row column -> row * 4 + column)
let doubled = grid |> Array2D.map (fun value -> value * 2)
let snapshot = Array2D.copy doubled
printn $"{snapshot.GetLength(0)} x {snapshot.GetLength(1)}; last = {snapshot[2, 3]}"
// 3 x 4; last = 22
Array2D.create, init och rebase använder den nollbaserade allokeringsvägen i alla Core-versioner. Bounds-preserving map, mapioch copy kan använda ett nyare .NET allokerings-API i net10.0 versionen. .NET Standard-versionerna behåller sin reservlösning. Arrayer som inte är nollbaserade är inte utfasade, men deras inbyggda allokeringsbegränsningar kvarstår.
Se Tvådimensionella matriser och Native AOT för allokeringssökvägar och återstående begränsningar. Kontrollera hela applikationens publiceringsdiagnostik.
Tack till @T-Gro för Array2D-förbättringarna.
Mindre minnesanvändning och mindre upprepat arbete
F# 11 minskar upprepad kompilatoranalys och vissa programallokeringar. Dessa påverkar olika arbetslaster.
Dela refererade bibliotek mellan projekt
För att undvika upprepade kopior av delade binära beroenden kan språktjänsten dela lämpliga importerade biblioteksgrafer mellan projekt.
I implementerings-PR:s experiment för behållet minne föll ReSharper.FSharp-lösningen för tio projekt från 1 547,2 MB till 1 002,2 MB med delning aktiverad. Det är en av/på-mätning för den arbetsbelastningen, inte en generell minskning från F# 10 till 11. Lösningar som domineras av projektreferenser snarare än delade binära referenser kan se mycket mindre vinster.
Metadataläsning och inline-specialisering minskar också tillfälliga allokeringar, vilket är ett separat mått. Se Verktygsprestanda för information om omfattning och kontroller.
Tack till @auduchinok för import av delade bibliotek.
Kompilera oberoende uppgifter parallellt som standard
Parallellkompilering är nu aktiverat som standard, med deterministisk utdata och regler för källordning bevarade. Förbättringar i cachelagring för överlagringar och guardade mönster undviker annat arbete som annars skulle upprepas. Visual Studio/FCS minskar också analys av oförändrade dokument och begränsar samtidiga kontroller; se Verktygsprestanda.
Undvik omkostnader för callback och tom resultatallokering
Markerade List- och Array-funktioner har nu stöd för inline-återanrop. För dina egna inline-hjälpare anpassar OptimizeClosureIfNotInlined en opakt curryfierad callback en gång:
let inline foldItems
([<InlineIfLambda; OptimizeClosureIfNotInlined>] folder)
state
(items: 'T[]) =
let mutable total = state
for item in items do
total <- folder total item
total
Andra allokeringar kan finnas kvar. Se Optimera curryfierade callback-funktioner för information om kraven.
Tomma resultat är ett annat vanligt fall av lyckad bearbetning. FSharpLints motiverande arbetsbelastning kombinerade upprepade gånger regler som inte hittade några varningar. Core återanvänder nu delade tomma arrayer i fler åtgärder:
let checkTitle (title: string) =
if String.IsNullOrWhiteSpace title then [|"A title is required."|]
else Array.empty
let warnings = ["Introduction"; "Installation"] |> List.toArray |> Array.collect checkTitle
let sharedEmpty = obj.ReferenceEquals(warnings, Array.empty<string>)
// true: no new empty result array
Ändringen tar bort redundanta tomma arrayobjekt, inte alla allokeringar i pipelinen. Förlita dig inte på att separata tomma resultat har olika referensidentiteter.
Tack vare @T-Gro för inline-återanrop för samlingar och delade tomma matriser.
Mer exakta API-anrop och vardagliga bekvämligheter
Kräv namn när genererad parameterordning kan ändras
För att skydda genererade API:er från tysta ändringar i parameterordningen kan en författare kräva namngivna argument:
Note
Enforcement är en F# 11.2-funktion; i .NET 11.0.100 SDK aktivera förhandsversion. En tillgänglig System.Diagnostics.CodeAnalysis.RequireNamedArgumentsAttribute definition krävs också. Det fullständiga exemplet tillhandahåller en kompatibilitetsdefinition för referenser som saknar den.
open System.Diagnostics.CodeAnalysis
type Query =
[<RequireNamedArguments>]
static member Execute(numerator: decimal, denominator: decimal) =
numerator / denominator
let ratio = Query.Execute(numerator = 3m, denominator = 4m)
// 0.75M; Query.Execute(3m, 4m) is rejected.
Att ändra ordningen på deklarationen bevarar det namngivna anropets resultat, 0.75M; ett positionsanrop avvisas. För användningsfallet generated-API måste providern generera annoteringen.
Känn igen ett redan omslutet generiskt argument
För att undvika att omsluta en befintlig uppgift som ett vanligt resultatvärde föredrar en ny överlagringsregel i sista hand Task<'T> framför enkla 'T:
Note
Det mest konkreta utslagskriteriet och beteendet för överbelastningsprioritet är funktioner i F# 11.2; i .NET 11.0.100 SDK:n aktivera förhandsversion.
open System.Threading.Tasks
let pending = Task.FromResult 42
let wrapped = ValueTask<_>(pending)
let answer: int = wrapped.Result
// ValueTask<int>, not ValueTask<Task<int>>
Resultatet är ValueTask<int>, inte ValueTask<Task<int>>. Se Överbelastningsval för regelns gränser.
Föredra en ny överlagrad variant utan att ta bort den gamla ingångspunkten
Ett bibliotek kan behålla en gammal diagnostikmetod för befintliga binärfiler samtidigt som en överlagrad funktion läggs till som innehåller anroparens källkodsplats.
OverloadResolutionPriorityAttribute gör den inställningen explicit för nyligen kompilerade anropare:
open System.Runtime.CompilerServices
type Check =
static member That(condition: bool) =
if condition then "OK" else "Check failed."
[<OverloadResolutionPriority(1)>]
static member That(condition: bool, [<CallerLineNumber>] ?line: int) =
if condition then "OK" else $"Check failed at line {defaultArg line 0}."
let stock = 0
let diagnostic = Check.That(stock > 0)
// The diagnostic includes the caller's source line.
Det oförändrade anropet väljer den radmedvetna överlagrade versionen. Se Val av överlagringar för omfångs- och kompatibilitetsregler.
Använd .NET:s villkorliga nullabilitetslöften
Om din filsökväg inte är null bör ett sökvägs-API:s nullbara resultat inte tvinga fram en kontroll som dess eget kontrakt utesluter. F# 11 förbrukar det befintliga .NET NotNullIfNotNull löfte: ett icke-null-namngivet indata innebär ett resultat som inte är null.
open System.IO
let fileName: string = Path.GetFileName("reports/annual.csv")
let extension: string = Path.GetExtension(fileName)
printn (extension.ToUpperInvariant())
// .CSV
let fileNameOf (path: string | null) : string | null =
Path.GetFileName path
Aktivera <Nullable>enable</Nullable>. Nullable-indata kräver fortfarande null-hantering; se Villkorsstyrda returkontrakt. För obegränsade C# Unchecked.withNull API:er, använd den separata T?.
Tack vare @kerams för stöd för villkorlig nullabilitet.
Mindre språkförbättringar
Om du vill anropa en posttyp med .NET-konstruktorsyntax använder du posttypskonstruktorer i F# 11.2:
type Person = { Name: string; Age: int }
let person = Person(Name = "Ada", Age = 36)
Namngivna argument undviker beroende av fältordning. Tack vare @charlesroddie för den här funktionen.
För villkorsstyrda källor undviker #elif kapslade #if-grupper:
#if EU_WAREHOUSE
let dispatchTimeZone = "Europe/Paris"
#elif US_WAREHOUSE
let dispatchTimeZone = "America/New_York"
#else
let dispatchTimeZone = "UTC"
#endif
System.Console.WriteLine(dispatchTimeZone)
Den första matchande gren vinner. Exempelprojektet definierar EU_WAREHOUSE och skriver ut Europe/Paris; när ingen av symbolerna är definierad är utdata UTC.
För relaterade verktygs- och interoperabilitetsscenarier:
- Om du vill identifiera din interaktiva verktygskedja använder du
#version. - Om du vill dela källan med en extern värd accepterar F#
#:rader med värddirektiv utan att tolka dem som paket- eller bygginstruktioner. - Använd standardimplementeringen för ett härlett gränssnitt för att undvika redundanta gränssnittsdeklarationer.
- Om du vill läsa ärvda tillstånd i en vanlig stängning använder du det nya stödet för skyddade basfält.
Innan du uppgraderar
Tänk på följande ändringar när du uppgraderar:
| Påverkad kod | Action |
|---|---|
Bibliotek med .fsi filer |
Framtvingade kontraktsattribut kopieras till signaturer; utelämnanden är nu FS3888-fel. |
| Inbäddade bibliotek som stöds av tillägg | Granska härledda signaturer och krav för producent/konsument. F# 11.2-inferens kan bevara mer generella begränsningar. |
| Ombud för instansmetod | Validera mottagare före direktkonstruktion, när fel för null-mottagare nu uppstår. |
| Lokaliserade interpolerade utdata | .NET-formatplatshållare använder den invarianta kulturen. Välj en explicit kultur när lokaliserade utdata är avsedda. |
Anpassade byggverktyg med final do! |
Bindning och enhetsslutförande; do! slutlig väljer inte längre medlemmar för vidarebefordran till slutläge. |
Ofärdig let! |
Lägg till följande beräkningskropp; en ofullständig bindning har nu en mer direkt diagnostik. |
| Äldre språkinställningar | De numrerade lägen som stöds börjar från och med F# 8; läget för icke-strikt indrag har tagits bort. Se Språkversion och indrag. |
| Enum-bitvisa operationer | Använd enumtyper med heltalsbaserad underliggande typ; ogiltiga underliggande typer avvisas. |