Notatka
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Witamy w języku F# 11! Ta wersja ułatwia zachowanie odrębnych modeli domeny, ponowne używanie ogólnych algorytmów, koordynowanie pracy asynchronicznej i zmniejszanie użycia pamięci kompilatora.
- Rozszerzanie rekordów ponownie wykorzystuje pola bez wymuszania, aby modele magazynu i aplikacji współdzieliły ten sam typ.
- Składowe rozszerzające w algorytmach generycznych umożliwiają istniejącym typom udostępnianie operacji bez klas opakowujących.
- .NET integracja zachowuje metadane delegata i ponownie używa dokumentacji interfejsu API.
- Asynchronizatory i pomocnicy zadań przenoszą anulowanie między granicami interfejsu API i ograniczają współbieżną pracę.
- Ulepszenia kompilatora i biblioteki zmniejszają powtarzaną analizę i unikają niektórych alokacji.
Dziękujemy społeczności języka F# za propozycje, implementacje, przeglądy i testy dotyczące tych zmian. Linki współtwórców towarzyszą poniższym elementom.
Note
Przykłady wymagają kompilatora języka F# 11 i biblioteki FSharp.Core 11 z pokazanymi funkcjami. W zestawie SDK .NET 11.0.100 funkcje języka F# 11.2 wymagają <LangVersion>preview</LangVersion>; zobacz Ustawienia języka i planowana obsługa zestawu SDK. Przykład runtime-async ma dodatkowe wymagania dotyczące środowiska uruchomieniowego; sam starszy pakiet .NET 11 SDK może nie zawierać wszystkich zmian.
Rozkłady rekordów: odrębne modele bez powtarzających się mapowań
Aby zachować odrębne modele magazynu i aplikacji, rozszerzenia rekordów ponownie wykorzystują swoje wspólne pola bez powtarzających się deklaracji. W tym miejscu modele udostępniają szczegóły klienta, ale używają różnych typów identyfikatorów:
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 wnosi deklaracje pól. Na granicy, ...row kopiuje wartości, podczas gdy konwersja analizuje identyfikator:
let toCustomer (row: CustomerRow) =
match Guid.TryParse row.Id with
| true, id -> Ok ({ ...row; Id = CustomerId id }: Customer)
| false, _ -> Error "Invalid customer identifier."
Miejsce docelowe przechowuje udostępnione szczegóły, zastępuje Id i wyklucza InternalNote.
Guid.TryParse, a nie operator rozwinięcia, weryfikuje identyfikator; źle sformułowane dane wejściowe generują wartość Error.
To dotyczy problemu mapowania warstwy aplikacji. Spready wartości łączą również rekordy nazwane i anonimowe; spready typu tworzą tylko nazwane rekordy. Późniejsze pola mają pierwszeństwo, a źródła są uruchamiane jednokrotnie, po kolei. Zobacz Operatory spreadów rekordów , aby zapoznać się z przykładami kompozycji i ryzykiem związanym z ewolucją współdzieloną.
Dzięki @brianrourkeboll za rekordowe rozpiętości.
Składowe rozszerzające dla ogólnych algorytmów, operatorów i krotki
Aby udostępnić algorytm dla istniejących typów, język F# 11.2 umożliwia członkom rozszerzeń spełniać statycznie rozstrzygane ograniczenia parametrów typu (SRTP). Na przykład jedno przekształcenie może obsługiwać listy i opcje bez typów opakowujących.
Note
W zestawie SDK .NET 11.0.100 włącz <LangVersion>preview</LangVersion>, gdzie są rozpoznawane rozszerzenia, i otwórz ich moduł. Zobacz wymagania dotyczące biblioteki.
Udostępnij transformację między listami i opcjami
Mając map biblioteki w zasięgu, aplikacja może wyczyścić dowolną z obu reprezentacji, zmienić typy elementów i utworzyć przekształcenia:
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]
Adaptery w Structural.Adapters wykorzystują znane operacje, zamiast narzucać nowe kontenery:
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
Funkcja współdzielona w Structural.Algorithms pyta typ wejściowy o statyczny element członkowski Map:
let inline map (mapping: 'T -> 'U) (source: ^Input) : ^Output =
(^Input :
(static member Map: ^Input * ('T -> 'U) -> ^Output)
(source, mapping))
^Input i ^Output są typami rozstrzyganymi statycznie. W każdym konkretnym wywołaniu kompilator wybiera element członkowski o wymaganej sygnaturze. Rozszerzenia nie dodają implementacji interfejsu.
Pełna aplikacja otwiera oba moduły.
Operatory, kontekst i typy wyjściowe
Ten sam mechanizm obsługuje operatory krotki oraz propagację kontekstu i konstrukcję sterowaną wynikiem. Natywne nagłówki krotki wymagają włączenia funkcji języka F# 11.2 w swoich deklaracjach, a także w miejscach wywołania związanych z rozwiązywaniem rozszerzeń.
Osobny dodatek, AllowOverloadOnReturnType, pozwala jednej fabryce zadeklarować przeciążenia rozróżniane przez ich zwroty:
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"
Ten atrybut Core 11 nie wymaga wersji zapoznawczej. Adnotacja wynikowa wybiera przeciążenie; bez niej wywołanie pozostaje niejednoznaczne. Zobacz Wybieranie według typu zwracanego.
Dzięki @gusty za rozszerzenie obsługi programowania generycznego.
Integracja z platformą .NET i środowisko deweloperskie
Delegaci bezpośredni zachowują rzeczywisty interfejs procedury obsługi
Aby udostępnić frameworkom nazwy procedur obsługi i atrybuty powiązań, F# 11 może bezpośrednio odwoływać się do zgodnej nazwanej funkcji zamiast do funkcji opakowującej. ASP.NET Core minimalne API mogą następnie odczytywać kontrakt trasy, usługi i nagłówka:
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)
Zobacz runnable project and construction/compatibility rules (Reguły tworzenia/zgodności). Przechwycenia i adaptacja argumentów mogą nadal wymagać funkcji opakowujących.
Dzięki @kerams za bezpośrednie delegaty.
Ponowne wykorzystanie dokumentacji XML
Aby utrzymywać wspólne uwagi dotyczące interfejsu API w jednym miejscu, umieść je w shared.xml:
<docs>
<tracking>
<remarks>An unrecognized tracking identifier has status Unknown.</remarks>
</tracking>
</docs>
Po włączeniu GenerateDocumentationFile język F# 11 rozwija <include> do generowanych wpisów składowych:
/// <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)
Implementacja include aktualizuje oba opisy z jednego fragmentu. Oddzielnie resolver narzędzi udostępnia opisy bazowe dla <inheritdoc/>; kompilator pozostawia ten tag odbiorcom dokumentacji. Zobacz dokumentację XML, aby zapoznać się z przykładami i zasadami rozstrzygania.
Dzięki @T-Gro dla udostępnionej dokumentacji XML w języku FSharp.Core.
Debugowanie funkcji inline w kompilacjach bez optymalizacji
Aby sprawdzić argumenty funkcji pomocniczej inline, ustaw punkt przerwania na let total w nieoptymalizowanej kompilacji i wejdź do addFee. F# 11 preferuje teraz metody, które można wywołać, w tych kompilacjach:
let inline addFee (fee: decimal) (amount: decimal) =
let total = amount + fee
total
let invoiceTotal = addFee 2m 40m
printfn "%M" invoiceTotal
// 42
Zoptymalizowane kompilacje zachowują wstawianie funkcji w miejscu wywołania. Niektóre konstrukcje nadal muszą się rozszerzać. Zobacz Debugowanie funkcji inline, aby uzyskać informacje o tych ograniczeniach i formancie --always-inline.
Asynchroniczność i koordynacja zadań
Aby połączyć przepływy pracy F# z interfejsami API opartymi na zadaniach, biblioteka FSharp.Core 11 dodaje mechanizmy pośredniczące z obsługą anulowania, partie o ograniczonym rozmiarze oraz przekształcenia wyników.
Ograniczać oczekujące operacje wejścia/wyjścia, a nie tylko zbiór wyników
Duża partia odczytów może uruchomić więcej zadań, niż aplikacja powinna mieć jednocześnie w toku. Aby zdecydować, kiedy rozpoczyna się praca oparta na zadaniach, koordynator potrzebuje metod wytwórczych, a nie zadań, które zostały już uruchomione:
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
Maksymalnie cztery operacje odczytu są wykonywane równolegle; wyniki zachowują kolejność danych wejściowych. Wystąpienie błędu powoduje anulowanie operacji równoległych przy użyciu dostarczonych tokenów. Ogranicza to współbieżność, a nie żądania na sekundę. Zobacz Funkcje asynchroniczne i funkcje zadań, aby uzyskać informacje o wariantach sekwencyjnych i zwracających wynik jednostkowy.
Zachowaj anulowanie i pierwotny wyjątek po obu stronach granicy
Gdy przepływ pracy uruchamia zewnętrzne zadanie, jego token powinien towarzyszyć temu zadaniu.
Async.StartTaskImmediate przekazuje token uruchomionego przepływu pracy do fabryki zadań i czeka na ukończenie:
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
}
Pojedyncze niepowodzenie zadania trafia do tej procedury obsługi jako bazowy FileNotFoundException. W przypadku istniejącego już zadania lub zadania zwracającego wartość użyj Async.Await; starsze Async.AwaitTask zachowuje swoje działanie polegające na opakowywaniu wyjątków.
W przypadku wywołań Task.startAsyncImmediate token workflow opartych na zadaniach zapewnia mostek odwrotny. Rozpoczyna się w wątku wywołującym do pierwszego asynchronicznego zawieszenia.
Używaj odpowiednich funkcji pomocniczych do krótkich przekształceń
Aby przekształcić jeden wynik bez użycia pełnego wyrażenia obliczeniowego, użyj odpowiednich funkcji pomocniczych w Microsoft.FSharp.Control. Na przykład odczytać plik, uzyskać jego długość i zwrócić zwykłe niepowodzenie jako Error:
open System.IO
open System.Threading
let fileLength (path: string) (ct: CancellationToken) =
File.ReadAllTextAsync(path, ct)
|> Task.map String.length
|> Task.catch
Plik zawierający Report 1 daje Ok 8; brakujący plik daje Error zawierający FileNotFoundException. Anulowanie jest nadal przekazywane.
Async i ValueTask oferują odpowiadające map, bind oraz funkcje pomocnicze obsługi błędów; zobacz Funkcje asynchroniczne i zadania. Użyj wyrażenia obliczeniowego dla gałęzi lub kilku wartości zależnych.
Metody pomocnicze ValueTask wymagają kompilacji Core net10.0 lub netstandard2.1 oraz jednorazowego użycia.
Dzięki @bartelink za wkład w API async/task.
Ponowne zgłoszenie po asynchronicznym zgłoszeniu błędu
Rejestrowanie operacyjne powinno dodawać informacje bez zmieniania tego, co przechwytuje kod wywołujący, ani ukrywania miejsca, w którym rozpoczęła się awaria. Moduły obsługi wyrażeń obliczeniowych mogą teraz używać reraise () nawet po wywołaniu await dla loggera:
Note
Rethrow wyrażenia obliczeniowego to funkcjonalność języka F# 11.2. W zestawie SDK .NET 11.0.100 włącz <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 ()
}
Po pomyślnym zalogowaniu propagują się ten sam wyjątek i oryginalne informacje o miejscu zgłoszenia wyjątku. Awaria loggera może zastąpić oryginał. To rozwiązuje żądanie dotyczące asynchronicznej obserwowalności bez asynchronicznego środowiska uruchomieniowego. Zobacz Ponowne zgłoszenie wyjątku z wyrażenia obliczeniowego.
Dzięki @xperiandri za to wsparcie.
FSharp.Core może bazować na nowoczesnym środowisku .NET
Aby korzystać z nowoczesnych API bez rezygnacji ze starszych platform docelowych, FSharp.Core udostępnia net10.0 obok netstandard2.0 i netstandard2.1. NuGet wybiera zgodną kompilację. Zaktualizowany sposób pakowania narzędzi umożliwia również asynchroniczne zwalnianie zasobów oparte na zadaniach w środowisku F# Interactive, podobnie jak w kompilowanych projektach. Zobacz platformy docelowe dla FSharp.Core.
Dziękujemy za @T-Gronowoczesną obsługę platformy Core i pakietowanie narzędzi SDK.
Asynchroniczność środowiska uruchomieniowego dla autorów buildera
Autorzy builderów mogą zezwolić środowisku uruchomieniowemu na wstrzymanie i wznowienie oznaczonych metod zamiast używania maszyn stanu generowanych przez kompilator. Eksperymentalna konstrukcja typu builder, zdefiniowana w bibliotece, udostępnia ten mechanizm jako wyrażenie obliczeniowe:
let readManifest (path: string) =
runtimeTask {
let! text = System.IO.File.ReadAllTextAsync path
return text.Length
}
Note
Ten eksperymentalny konstrukt korzysta z funkcji języka F# 11.2, która umożliwia zdefiniowanie lub rozwinięcie jego implementacji śródliniowej; w zestawie SDK platformy .NET 11.0.100 włącz obsługę wersji zapoznawczej. Wymaga również kompilacji i odwołań Core 11 net10.0 oraz środowiska uruchomieniowego z obsługą runtime-async. Zweryfikowana kombinacja środowiska uruchomieniowego i biblioteki referencyjnej to .NET 11 RC1. Samo ukierunkowanie na platformę .NET 10 nie jest wystarczające. Zobacz Asynchroniczne eksperymenty środowiska uruchomieniowego.
FSharp.Core nie dostarcza tego przykładu jako kreatora ogólnego przeznaczenia. Zwykły kod task i async nie przełącza się na to automatycznie, a kod wywołujący już skompilowany zwykły interfejs API zwracający obiekt Task sam w sobie nie wymaga funkcji w wersji zapoznawczej.
Dzięki @majocha za asynchroniczne środowisko uruchomieniowe.
Tekst zgodny z Native AOT i tablice dwuwymiarowe
Aby obsługiwać natywną AOT, język F# 11 usuwa wymagania dotyczące formatowania opartego na odbiciu i dynamicznej alokacji z bardziej typowych operacji tekstowych i tablicowych.
Wypisuj wartości i twórz ciągi znaków bez zbędnej warstwy formatowania
Jeśli masz już łańcuch znaków, nie potrzebujesz formatu "%s" tylko do jego zapisania. Nowe funkcje Core print i printn akceptują wartości poprzez konwersję string w F#; printn dodaje znak nowej linii:
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
Interpolacja ciągów używa konwersji bezpośredniej i String.Concat w wielu przypadkach. Pola formatujące .NET, takie jak {total:F2}, używają formatowania niezmiennego kulturowo i pakują wartość; %A oraz formy printf z określeniem precyzji mogą nadal wymagać formatowania printf. Wybierz jawną kulturę dla zlokalizowanych danych wyjściowych.
Dzięki --reflectionfree rekordy i typy unii również otrzymują generowane ToString wyjście. Zobacz Interpolacja i Native AOT, aby zapoznać się z przykładami ustawień regionalnych i obsługiwanymi ścieżkami.
Dzięki @bbatsov za funkcje drukowania i @charlesroddie za implementację interpolacji.
Zachowaj zwykłe operacje siatki indeksowanej od zera
Siatka oparta na zera nie powinna wymagać zastępczego modułu Array2D, aby uniknąć ścieżki alokacji kodu dynamicznego. Zaktualizowane implementacje podstawowe obsługują znane inicjowanie i przekształcanie:
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 i rebase używają ścieżki alokacji o indeksowaniu od zera we wszystkich kompilacjach Core.
map, mapi i copy z zachowaniem granic mogą używać nowszego interfejsu API alokacji .NET w kompilacji net10.0; kompilacje .NET Standard zachowują mechanizm zastępczy. Tablice o indeksowaniu innym niż od zera nie są przestarzałe, ale ich natywne ograniczenia alokacji pozostają.
Zobacz Tablice dwuwymiarowe i macierzysty AOT , aby uzyskać ścieżki alokacji i pozostałe ograniczenia. Sprawdź diagnostykę publikowania kompletnej aplikacji.
Dzięki @T-Gro za ulepszenia Array2D.
Mniejsze zużycie pamięci i mniej powtórnej pracy
Język F# 11 zmniejsza powtarzaną analizę kompilatora i niektóre alokacje aplikacji. Mają one wpływ na różne obciążenia robocze.
Udostępnianie bibliotek, do których odwołuje się odwołanie w projektach
Aby uniknąć powtarzających się kopii współużytkowanych zależności binarnych, usługa językowa może udostępniać kwalifikujące się grafy biblioteki importowanej między projektami.
W eksperymencie retained-memory implementującego PR-a rozwiązanie ReSharper.FSharp obejmujące dziesięć projektów spadło z 1547,2 MB do 1002,2 MB przy włączonym udostępnianiu. Jest to pomiar off/on dla tego obciążenia, a nie ogólna redukcja F# z 10 do 11. Rozwiązania zdominowane przez odwołania do projektu, a nie współużytkowane odwołania binarne, mogą przynieść znacznie mniejsze korzyści.
Odczyt metadanych i specjalizacja śródliniowa zmniejszają również tymczasowe alokacje— oddzielną metrykę. Zobacz Wydajność narzędzi, aby uzyskać informacje o zakresie i kontrolkach.
Dzięki @auduchinok do importowania bibliotek udostępnionych.
Domyślnie kompiluj niezależne zadania równolegle
Kompilacja równoległa jest teraz domyślnie włączona z zachowaniem reguł deterministycznych danych wyjściowych i kolejności źródła. Przeciążenie buforowania i ulepszenia wzorca chronionego pozwalają uniknąć innych powtórzeń pracy. Visual Studio/FCS również zmniejszyć bez zmian analizę dokumentów i powiązane testy współbieżne; zobacz Wydajność narzędzi.
Unikaj obciążeń związanych z wywołaniem zwrotnym i alokacją pustych wyników
Wybrane funkcje List i Array obsługują teraz wywołania zwrotne inline. Dla własnych pomocników OptimizeClosureIfNotInlined wbudowanych dostosowuje nieprzezroczyste wywołanie zwrotne po kolei:
let inline foldItems
([<InlineIfLambda; OptimizeClosureIfNotInlined>] folder)
state
(items: 'T[]) =
let mutable total = state
for item in items do
total <- folder total item
total
Inne alokacje mogą pozostać. Zobacz Optymalizowanie wywołań zwrotnych curried, aby poznać wymagania.
Puste wyniki to kolejny typowy przypadek prawidłowego działania. Obciążenie będące motywacją dla FSharpLint wielokrotnie łączyło reguły, które nie zgłaszały żadnych ostrzeżeń. Core teraz ponownie wykorzystuje współużytkowane puste tablice w kolejnych operacjach:
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
Zmiana usuwa nadmiarowe obiekty typu empty-array, a nie każdą alokację w potoku przetwarzania. Nie należy zakładać, że oddzielne puste wyniki mają odrębne tożsamości referencyjne.
@T-Gro Dzięki za callbacki kolekcji inline i współdzielone puste tablice.
Bardziej precyzyjne wywołania interfejsu API i codzienne wygody
Wymagaj nazw, gdy wygenerowana kolejność parametrów może ulec zmianie
Aby chronić wygenerowane interfejsy API przed niezauważalnymi zmianami kolejności parametrów, autor może wymagać nazwanych argumentów:
Note
Wymuszanie jest funkcją języka F# 11.2; w zestawie SDK .NET 11.0.100 włącz wersję zapoznawcza. Wymagana jest również dostępna System.Diagnostics.CodeAnalysis.RequireNamedArgumentsAttribute definicja.
Kompletny przykład dostarcza definicję zgodności dla odwołań, którym jej brakuje.
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.
Zmiana kolejności elementów w deklaracji powoduje zachowanie wyniku nazwanego wywołania, 0.75M; wywołanie pozycyjne jest odrzucane. W przypadku przypadku użycia wygenerowanego interfejsu API dostawca musi emitować adnotację.
Rozpoznawanie już opakowanego argumentu generycznego
Aby uniknąć zawijania istniejącego zadania jako wartości zwykłego wyniku, nowa reguła przeciążenia ostatniego resortu preferuje Task<'T> na gołym 'T:
Note
Najbardziej konkretne rozstrzyganie remisów i zachowanie priorytetu przeciążenia to funkcje języka F# 11.2; w zestawie SDK .NET 11.0.100 włącz wersję zapoznawczą.
open System.Threading.Tasks
let pending = Task.FromResult 42
let wrapped = ValueTask<_>(pending)
let answer: int = wrapped.Result
// ValueTask<int>, not ValueTask<Task<int>>
Wynik to ValueTask<int>, a nie ValueTask<Task<int>>. Zobacz Wybór przeciążenia, aby poznać ograniczenia reguły.
Należy preferować nowe przeciążenie bez usuwania starego punktu wejścia
Biblioteka może zachować starą metodę diagnostyczną dla istniejących plików binarnych podczas dodawania przeciążenia zawierającego lokalizację w kodzie źródłowym miejsca wywołania.
OverloadResolutionPriorityAttribute sprawia, że ta preferencja jest jawna dla nowo skompilowanego kodu wywołującego:
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.
Niezmienione wywołanie wybiera przeciążenie uwzględniające wiersze. Zobacz Wybieranie przeciążenia, aby uzyskać informacje o regułach zakresu i zgodności.
Użyj warunkowych gwarancji wartości null platformy .NET
Jeśli ścieżka pliku nie ma wartości null, wynik dopuszczający wartość null interfejsu API ścieżki nie powinien wymusić sprawdzenia, które jego własny kontrakt wyklucza. Język F# 11 korzysta z istniejącej obietnicy .NETNotNullIfNotNull: nazwane dane wejściowe inne niż null oznaczają wynik inny niż 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
Włącz <Nullable>enable</Nullable>. Dane wejściowe dopuszczane do wartości null nadal wymagają obsługi dopuszczania wartości null; zobacz Warunkowe kontrakty powrotne. W przypadku interfejsów API języka C# T? bez ograniczeń użyj oddzielnego Unchecked.withNull pomocnika.
Dzięki @kerams za wsparcie dla warunkowej dopuszczalności wartości null.
Drobne ulepszenia językowe
Aby utworzyć rekord przy użyciu składni konstruktora .NET, użyj konstruktorów rekordów F# 11.2:
type Person = { Name: string; Age: int }
let person = Person(Name = "Ada", Age = 36)
Nazwane argumenty eliminują zależność od kolejności pól. Dzięki @charlesroddie za tę funkcję.
W przypadku warunkowego źródła #elif pozwala uniknąć zagnieżdżonych grup #if:
#if EU_WAREHOUSE
let dispatchTimeZone = "Europe/Paris"
#elif US_WAREHOUSE
let dispatchTimeZone = "America/New_York"
#else
let dispatchTimeZone = "UTC"
#endif
System.Console.WriteLine(dispatchTimeZone)
Pierwsza zgodna gałąź wygrywa. Przykładowy projekt definiuje EU_WAREHOUSE i drukuje Europe/Paris; bez zdefiniowania żadnego z symboli dane wyjściowe to UTC.
W przypadku powiązanych przypadków narzędzi i interoperacyjności:
- Aby zidentyfikować interakcyjny łańcuch narzędzi, użyj polecenia
#version. - Aby udostępnić źródło hostowi zewnętrznemu, język F# akceptuje wiersze
#:dyrektywy hosta bez interpretowania ich jako instrukcji dotyczących pakietu lub kompilacji. - Aby uniknąć nadmiarowych deklaracji interfejsu, użyj domyślnej implementacji interfejsu pochodnego.
- Aby odczytać stan dziedziczony wewnątrz zwykłego domknięcia, użyj nowej obsługi pól klasy bazowej o modyfikatorze protected.
Przed uaktualnieniem
Pamiętaj o tych istotnych zmianach podczas aktualizacji:
| Kod, którego dotyczy problem | Action |
|---|---|
Biblioteki z plikami .fsi |
Kopiowanie wymuszonych atrybutów kontraktu do podpisów; braki są teraz błędami FS3888. |
| Biblioteki osadzone obsługiwane przez rozszerzenia | Przejrzyj wywnioskowane podpisy i wymagania producenta/konsumenta. Wnioskowanie języka F# 11.2 może zachować bardziej ogólne ograniczenia. |
| Delegaty metody wystąpienia | Zweryfikuj obiekty odbierające przed bezpośrednim tworzeniem, gdy dochodzi teraz do błędów związanych z obiektem odbierającym o wartości null. |
| Zlokalizowane interpolowane dane wyjściowe | .NET format otworów używają niezmiennej kultury. Wybierz jawną kulturę, gdy mają być zlokalizowane dane wyjściowe. |
Własne kreatory z finalnym do! |
Zapewnienie wiązania i uzupełniania modułu; final do! nie wybiera już składowych przekazywanych dalej jako final. |
Niedokończone let! |
Dodaj następującą treść obliczeniową; niekompletne powiązanie ma teraz bardziej bezpośrednią diagnostykę. |
| Starsze ustawienia języka | Obsługiwane numerowane tryby zaczynają się od F# 8; tryb nieścisłych wcięć został usunięty. Zobacz Wersja językowa i wcięcie. |
| Operacje bitowe na wyliczeniach | Używaj typów wyliczeniowych z bazowym typem całkowitym; nieprawidłowe typy bazowe są odrzucane. |