Novinky v F# 9
F# 9 představuje řadu vylepšení, která usnadňují bezpečnější, odolnější a výkon vašich programů. Tento článek popisuje hlavní změny v F# 9, vyvinutém v open source úložišti kódu F#.
F# 9 je k dispozici v .NET 9. Nejnovější sadu .NET SDK si můžete stáhnout ze stránky pro stahování .NET.
Odkazové typy s možnou hodnotou null
I když je jazyk F# navržený tak, aby se vyhnul null
, může se tento jev objevit při používání knihoven .NET napsaných v jazyce C#. Jazyk F# teď nabízí bezpečný způsob, jak pracovat s odkazovými typy, které můžou mít null
jako platnou hodnotu.
Další podrobnosti najdete v příspěvku na blogu o nulovatelných odkazových typech ve F# 9.
Tady je několik příkladů:
// Declared type at let-binding
let notAValue: string | null = null
let isAValue: string | null = "hello world"
let isNotAValue2: string = null // gives a nullability warning
let getLength (x: string | null) = x.Length // gives a nullability warning since x is a nullable string
// Parameter to a function
let len (str: string | null) =
match str with
| null -> -1
| s -> s.Length // binds a non-null result - compiler eliminated "null" after the first clause
// Parameter to a function
let len (str: string | null) =
let s = nullArgCheck "str" str // Returns a non-null string
s.Length // binds a non-null result
// Declared type at let-binding
let maybeAValue: string | null = hopefullyGetAString()
// Array type signature
let f (arr: (string | null)[]) = ()
// Generic code, note 'T must be constrained to be a reference type
let findOrNull (index: int) (list: 'T list) : 'T | null when 'T : not struct =
match List.tryItem index list with
| Some item -> item
| None -> null
Diskriminované sjednocení .Is*
vlastností
Diskriminovaná sjednocení nyní mají automaticky generované vlastnosti pro každou variantu, což umožňuje ověřit, zda hodnota odpovídá konkrétní variantě. Například pro následující typ:
type Contact =
| Email of address: string
| Phone of countryCode: int * number: string
type Person = { name: string; contact: Contact }
Dříve jste museli napsat něco jako:
let canSendEmailTo person =
match person.contact with
| Email _ -> true
| _ -> false
Teď můžete místo toho psát:
let canSendEmailTo person =
person.contact.IsEmail
Částečné aktivní vzory můžou místo unit option
vracet bool
Dříve částečně aktivní vzory vracely Some ()
pro označení shody a None
jinak. Nyní mohou také vrátit bool
.
Například aktivní vzor pro následující:
match key with
| CaseInsensitive "foo" -> ...
| CaseInsensitive "bar" -> ...
Byl dříve napsán jako:
let (|CaseInsensitive|_|) (pattern: string) (value: string) =
if String.Equals(value, pattern, StringComparison.OrdinalIgnoreCase) then
Some ()
else
None
Teď můžete místo toho psát:
let (|CaseInsensitive|_|) (pattern: string) (value: string) =
String.Equals(value, pattern, StringComparison.OrdinalIgnoreCase)
Při zadání argumentů upřednostněte metody rozšíření k vnitřním vlastnostem.
Pro sladění se vzorem, který je vidět v některých knihovnách .NET, kde jsou metody rozšíření definované se stejnými názvy jako vnitřní vlastnosti typu, jazyk F# teď tyto metody rozšíření vyřeší místo selhání kontroly typu.
Příklad:
type Foo() =
member val X : int = 0 with get, set
[<Extension>]
type FooExt =
[<Extension>]
static member X (f: Foo, i: int) = f.X <- i; f
let f = Foo()
f.X(1) // We can now call the extension method to set the property and chain further calls
Výrazy výpočtů s prázdným obsahem
Jazyk F# teď podporuje výpočetní výrazy prázdné .
let xs = seq { } // Empty sequence
let html =
div {
p { "Some content." }
p { } // Empty paragraph
}
Zápis prázdného výpočetního výrazu způsobí volání metody Zero
tvůrce výpočetních výrazů.
Jedná se o přirozenější syntaxi v porovnání s dříve dostupnou builder { () }
.
Direktivy hash mohou přijímat neřetězcové argumenty.
Direktivy hash pro kompilátor dříve umožňovaly pouze řetězcové argumenty předané v uvozovkách. Teď můžou použít libovolný typ argumentu.
Dříve jste měli:
#nowarn "0070"
#time "on"
Teď můžete napsat:
#nowarn 0070
#time on
To se také váže k dalším dvěma změnám.
Rozšířená direktiva #help ve fsi pro zobrazení dokumentace v REPL
Direktiva #help
v jazyce F# Interactive teď zobrazuje dokumentaci pro daný objekt nebo funkci, kterou teď můžete předat bez uvozovek.
> #help List.map;;
Description:
Builds a new collection whose elements are the results of applying the given function
to each of the elements of the collection.
Parameters:
- mapping: The function to transform elements from the input list.
- list: The input list.
Returns:
The list of transformed elements.
Examples:
let inputs = [ "a"; "bbb"; "cc" ]
inputs |> List.map (fun x -> x.Length)
// Evaluates to [ 1; 3; 2 ]
Full name: Microsoft.FSharp.Collections.ListModule.map
Assembly: FSharp.Core.dll
Podrobnosti naleznete v blogovém příspěvku F# Interactive o vylepšení #help, viz
Povolit #nowarn, aby podporoval předponu FS u kódů chyb k zakázání upozornění
Dříve, když jste chtěli zakázat upozornění a napsali #nowarn "FS0057"
, dostanete Invalid warning number 'FS0057'
. I když je číslo upozornění správné, nemělo by mít předponu FS
.
Teď nebudete muset trávit čas tím, že zjistíte, že čísla upozornění jsou přijata i s předponou.
Všechny tyto funkce teď budou fungovat:
#nowarn 57
#nowarn 0057
#nowarn FS0057
#nowarn "57"
#nowarn "0057"
#nowarn "FS0057"
V celém projektu je vhodné použít stejný styl.
Upozornění na atribut TailCall u nerekurzivních funkcí nebo hodnot vázaných na let
Jazyk F# teď vygeneruje upozornění, když někam umístíte atribut [<TailCall>]
, který nepatří. I když nemá žádný vliv na to, co kód dělá, může zmást někoho, kdo ho čte.
Například tato použití teď vygenerují upozornění:
[<TailCall>]
let someNonRecFun x = x + x
[<TailCall>]
let someX = 23
[<TailCall>]
let rec someRecLetBoundValue = nameof(someRecLetBoundValue)
Vynucení cílů atributů
Kompilátor teď správně vynucuje AttributeTargets
pro hodnoty let, funkce, deklarace sjednocovacího případu, implicitní konstruktory, struktury a třídy. To může zabránit některým obtížně postřehnutelným chybám, jako je například zapomenutí přidat argument do unit testu Xunit.
Dříve jste mohli napsat:
[<Fact>]
let ``this test always fails`` =
Assert.True(false)
Když jste testy spustili s dotnet test
, prošly by. Vzhledem k tomu, že testovací funkce není ve skutečnosti funkcí, byla ignorována spouštěčem testů.
Nyní, se správným vynucováním atributů, získáte error FS0842: This attribute is not valid for use on this language element
.
Aktualizace standardní knihovny (FSharp.Core)
Náhodné funkce pro kolekce
Moduly List
, Array
a Seq
mají nové funkce pro náhodné vzorkování a náhodné prohazování. To usnadňuje použití jazyka F# pro běžné datové vědy, strojové učení, vývoj her a další scénáře, ve kterých je potřeba náhodnost.
Všechny funkce mají následující varianty:
- Ten, který používá implicitní, vláknově bezpečnou sdílenou instanci Random
- Jedna, která jako argument přebírá instanci
Random
- Který přijímá vlastní funkci
randomizer
, která by měla vrátit hodnotu typu float větší nebo rovnou 0,0 a menší než 1,0
K dispozici jsou čtyři funkce (každý se třemi variantami): Shuffle
, Choice
, Choices
a Sample
.
Zamíchat
Funkce Shuffle
vrátí novou kolekci stejného typu a velikosti s každou položkou v náhodně smíšené pozici. Šance, že skončí na libovolné pozici, je rovnoměrně vážená na délce kolekce.
let allPlayers = [ "Alice"; "Bob"; "Charlie"; "Dave" ]
let round1Order = allPlayers |> List.randomShuffle // [ "Charlie"; "Dave"; "Alice"; "Bob" ]
U polí existují také InPlace
varianty, které položky v existujícím poli prohazují místo vytvoření nové.
Volba
Funkce Choice
vrací jeden náhodný prvek z dané kolekce. Náhodná volba je rovnoměrně vážená podle velikosti kolekce.
let allPlayers = [ "Alice"; "Bob"; "Charlie"; "Dave" ]
let randomPlayer = allPlayers |> List.randomChoice // "Charlie"
Možnosti
Funkce Choices
vyberou z vstupní kolekce v náhodném pořadí N elementy, což umožňuje výběr prvků více než jednou.
let weather = [ "Raining"; "Sunny"; "Snowing"; "Windy" ]
let forecastForNext3Days = weather |> List.randomChoices 3 // [ "Windy"; "Snowing"; "Windy" ]
Ukázka
Funkce Sample
vyberou Z vstupní kolekce v náhodném pořadí N prvky, aniž by bylo možné vybrat více než jednou prvky. N nemůže být větší než délka kolekce.
let foods = [ "Apple"; "Banana"; "Carrot"; "Donut"; "Egg" ]
let today'sMenu = foods |> List.randomSample 3 // [ "Donut"; "Apple"; "Egg" ]
Úplný seznam funkcí a jejich variant najdete v dokumentu (RFC #1135).
Konstruktor bez parametrů pro CustomOperationAttribute
Tento konstruktor usnadňuje vytvoření vlastní operace pro tvůrce výpočetních výrazů. Místo explicitního pojmenování používá název metody (ve většině případů název odpovídá názvu metody).
type FooBuilder() =
[<CustomOperation>] // Previously had to be [<CustomOperation("bar")>]
member _.bar(state) = state
Podpora kolekčních výrazů jazyka C# pro seznamy a sady jazyka F#
Při použití seznamů a sad jazyka F# z jazyka C# teď můžete použít výrazy kolekce k jejich inicializaci.
Namísto:
FSharpSet<int> mySet = SetModule.FromArray([1, 2, 3]);
Teď můžete napsat:
FSharpSet<int> mySet = [ 1, 2, 3 ];
Výrazy kolekce usnadňují používání neměnných kolekcí jazyka F# z jazyka C#. Kolekce F# můžete chtít použít v případě, že potřebujete jejich strukturální rovnost, což System.Collections.Immutable kolekce nemají.
Vylepšení produktivity vývojářů
Obnovení analyzátoru
Neustále dochází ke zlepšování obnovy analyzátoru, což znamená, že nástroje (například zvýrazňování syntaxe) stále fungují s kódem, když jste uprostřed jeho úprav a nemusí být syntakticky správný.
Analyzátor se například obnoví u nedokončených vzorů as
, objektových výrazů, deklarací případů výčtového typu, deklarací záznamů, složitých vzorů primárního konstruktoru, nevyřešených dlouhých identifikátorů, prázdných klauzulí shody, chybějících polí případů sjednocení a chybějících typů polí případů sjednocení.
Diagnostika
Diagnostika nebo pochopení toho, co se kompilátoru vašemu kódu nelíbí, jsou důležitou součástí uživatelského prostředí s jazykem F#. V jazyce F# 9 existuje řada nových nebo vylepšených diagnostických zpráv nebo přesnějších diagnostických lokalizací.
Patří mezi ně:
- Nejednoznačná metoda override ve výrazu objektu
- Abstraktní členy při použití v ne abstraktních třídách
- Vlastnost, která má stejný název jako diskriminovaný případ sjednocení
- Neshoda počtu argumentů aktivního vzoru
- Struktury s duplicitními poli
- Použití
use!
sand!
ve výpočetních výrazech
Pro třídy s více než 65 520 metodami vygenerovaných ILexistuje také nová chyba v době kompilace . Tyto třídy nejsou načitatelné CLR a způsobují chybu během běhu. (Nebudete vytvářet tolik metod, ale existují případy s vygenerovaným kódem.)
Reálná viditelnost
Existuje zvláštnost v tom, jak F# generuje sestavení, což vede k tomu, že soukromí členové jsou zapsáni do IL jako interní členy. To umožňuje nevhodný přístup k soukromým členům z projektů mimo F#, které mají přístup k projektu F# prostřednictvím InternalsVisibleTo
.
Nyní je k dispozici volitelná oprava tohoto chování prostřednictvím kompilačního příznaku --realsig+
. Vyzkoušejte ho ve svém řešení, abyste zjistili, jestli některý z vašich projektů závisí na tomto chování. Můžete ho přidat do .fsproj
souborů takto:
<PropertyGroup>
<RealSig>true</RealSig>
</PropertyGroup>
Vylepšení výkonu
Optimalizované kontroly rovnosti
Kontroly rovnosti jsou nyní rychlejší a přidělují méně paměti.
Například:
[<Struct>]
type MyId =
val Id: int
new id = { Id = id }
let ids = Array.init 1000 MyId
let missingId = MyId -1
// used to box 1000 times, doesn't box anymore
let _ = ids |> Array.contains missingId
Výsledky srovnávacích testů pro dotčené maticové funkce použité u struktury se 2 členy
Před:
Metoda | Znamenat | Chyba | Gen0 | Přidělený |
---|---|---|---|---|
PoleObsahujeExistující | 15,48 ns | 0,398 ns | 0.0008 | 48 B |
PoleObsahujeNeexistující | 5 190,95 ns | 103,533 ns | 0.3891 | 24000 B |
ArrayExistsExisting | 17,97 ns | 0,389 ns | 0.0012 | 72 B |
ArrayExistsNonexisting | 5 316,64 ns | 103,776 ns | 0.3891 | 24024 B |
ArrayTryFindExisting | 24,80 ns | 0,554 ns | 0.0015 | 96 B |
PoleZkusNajítNeexistující | 5 139,58 ns | 260,949 ns | 0.3891 | 24024 B |
PokusNajítIndexExistující | 15,92 ns | 0,526 ns | 0.0015 | 96 B |
ArrayTryNajítIndexNenalezený | 4 349,13 ns | 100,750 ns | 0.3891 | 24024 B |
Po:
Metoda | Znamenat | Chyba | Gen0 | Přidělený |
---|---|---|---|---|
PoleObsahujeExistující | 4,865 ns | 0,3452 ns | - | - |
PoleObsahujeNeexistující | 766.005 ns | 15.2003 ns | - | - |
ArrayExistsExisting | 8.025 ns | 0.1966 ns | 0.0004 | 24 B |
ArrayExistsNonexisting | 834.811 ns | 16.2784 ns | - | 24 B |
ArrayTryFindExisting | 16.401 ns | 0,3932 ns | 0.0008 | 48 B |
ArrayTryFindNonexisting | 1 140,515 ns | 22.7372 ns | - | 24 B |
PoleZkusNajítIndexStávající | 14.864 ns | 0.3648 ns | 0.0008 | 48 B |
ArrayTryFindIndexNonexisting (Pokuste se najít index neexistujícího prvku v poli) | 990.028 ns | 19.7157 ns | - | 24 B |
Všechny podrobnosti si můžete přečíst tady: příběhy vývojářů F#: Jak jsme konečně opravili 9letý problém s výkonem.
Sdílení polí pro diskriminované sjednocení struktur
Pokud pole ve více případech diskriminované struktury mají stejný název a typ, můžou sdílet stejné umístění paměti, což snižuje nároky na paměť struktury. (Dříve nebyly povoleny stejné názvy polí, takže nedochází k žádným problémům s binární kompatibilitou.)
Například:
[<Struct>]
type MyStructDU =
| Length of int64<meter>
| Time of int64<second>
| Temperature of int64<kelvin>
| Pressure of int64<pascal>
| Abbrev of TypeAbbreviationForInt64
| JustPlain of int64
| MyUnit of int64<MyUnit>
sizeof<MyStructDU> // 16 bytes
Porovnání s předchozími veriony (kde jste museli používat jedinečné názvy polí):
[<Struct>]
type MyStructDU =
| Length of length: int64<meter>
| Time of time: int64<second>
| Temperature of temperature: int64<kelvin>
| Pressure of pressure: int64<pascal>
| Abbrev of abbrev: TypeAbbreviationForInt64
| JustPlain of plain: int64
| MyUnit of myUnit: int64<MyUnit>
sizeof<MyStructDU> // 60 bytes
Optimalizace integrálních rozsahů
Kompilátor teď generuje optimalizovaný kód pro více instancí start..finish
a výrazů start..step..finish
. Dříve byly optimalizovány pouze v případě, že byl typ int
/int32
a krok byl konstantní 1
nebo -1
. Jiné celočíselné typy a různé hodnoty kroku používaly neefektivní implementaci založenou na IEnumerable
. Teď jsou všechny optimalizované.
To vede ke zrychlení smyček v rozsahu od 1,25× až po 8×.
for … in start..finish do …
Výrazy seznamu a pole:
[start..step..finish]
a porozumění:
[for n in start..finish -> f n]
Optimalizované for x in xs -> …
v porozumění seznamům a polím
Pokud jde o související poznámku, výraz for x in xs -> …
byl optimalizován pro seznamy a pole, s pozoruhodnými vylepšeními zejména pro pole, přičemž došlo ke zvýšení rychlosti až o 10× a snížení velikosti přidělené paměti na 1/3 až 1/4.
Vylepšení nástrojů
Živé vyrovnávací paměti v sadě Visual Studio
Tato dříve volitelná funkce byla důkladně otestována a nyní je automaticky zapnutá. Kompilátor na pozadí, který používá integrované vývojové prostředí (IDE), nyní funguje se zpracováním paměti souboru v reálném čase, což znamená, že není třeba ukládat soubory na disk, abyste uplatnili změny. Dříve to mohlo způsobit neočekávané chování. (Nejvíce proslulé při pokusu o přejmenování symbolu v souboru, který byl upraven, ale nebyl uložen.)
Analyzátor a oprava kódu pro odebrání nepotřebných závorek
Někdy se závorky používají k přehlednosti, ale někdy jsou to jen šum. Ve druhém případě nyní získáte opravu kódu v aplikaci Visual Studio, abyste je odebrali.
Například:
let f (x) = x // -> let f x = x
let _ = (2 * 2) + 3 // -> let _ = 2 * 2 + 3
Podpora vlastního vizualizéru pro jazyk F# v sadě Visual Studio
Vizualizér ladicího programu v sadě Visual Studio teď funguje s projekty jazyka F#.
Popisy podpisů zobrazené uprostřed potrubí
Dříve se nápověda k signatuře nenabízela v situacích, jako je například následující: uprostřed pipeline měla funkce již použitý složitý parametr curried (například lambda). Nyní se zobrazí tooltip podpisu pro další parametr (state
):