Az F# 9 újdonságai
Az F# 9 számos olyan fejlesztést vezet be, amelyek biztonságosabbá, rugalmasabbá és teljesítményesebbé teszik a programokat. Ez a cikk a F# nyílt forráskódú kódtárbankifejlesztett F# 9 főbb változásait ismerteti.
Az F# 9 a .NET 9-ben érhető el. A legújabb .NET SDK-t a .NET letöltési oldaláról töltheti le.
Null értékű hivatkozástípusok
Bár az F# célja, hogy elkerülje a null
, a C#-ban írt .NET-kódtárakkal való együttműködés során be tud csúszni. Az F# mostantól típusbiztos módot kínál az olyan referenciatípusok kezelésére, amelyek érvényes értékként null
lehetnek.
További részletekért tekintse meg az F# 9 blogbejegyzés
Íme néhány példa:
// 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
.Is*
diszkriminált unió tulajdonságai
A diszkriminált uniók esetén mostantól minden egyes esetre automatikusan létrehozott tulajdonságok állnak rendelkezésre, így könnyedén ellenőrizheti, hogy egy érték tartozik-e egy adott esethez. Például a következő típushoz:
type Contact =
| Email of address: string
| Phone of countryCode: int * number: string
type Person = { name: string; contact: Contact }
Korábban a következőhöz hasonlót kellett írnia:
let canSendEmailTo person =
match person.contact with
| Email _ -> true
| _ -> false
Most ehelyett a következőt írhatja:
let canSendEmailTo person =
person.contact.IsEmail
A részleges aktív minták bool
adhatnak vissza unit option
helyett
Korábban a részleges aktív minták a Some ()
értéket adták vissza az egyezés jelzésére, és különben a None
-et. Most bool
is visszatérhetnek.
Például az alábbi aktív minta:
match key with
| CaseInsensitive "foo" -> ...
| CaseInsensitive "bar" -> ...
Korábban a következőképpen írták:
let (|CaseInsensitive|_|) (pattern: string) (value: string) =
if String.Equals(value, pattern, StringComparison.OrdinalIgnoreCase) then
Some ()
else
None
Most ehelyett a következőt írhatja:
let (|CaseInsensitive|_|) (pattern: string) (value: string) =
String.Equals(value, pattern, StringComparison.OrdinalIgnoreCase)
Az argumentumok megadásakor a bővítménymetelyeket részesíti előnyben a belső tulajdonságokhoz
Az egyes .NET-kódtárakban látható mintához igazodva, ahol a bővítménymetódusok ugyanazokkal a névvel vannak definiálva, mint egy típus belső tulajdonságai, az F# most ezeket a bővítménymetódusokat oldja fel a típusellenőrzés sikertelensége helyett.
Példa:
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
Üres testű számítási kifejezések
Az F# mostantól támogatja az üres számítási kifejezéseket.
let xs = seq { } // Empty sequence
let html =
div {
p { "Some content." }
p { } // Empty paragraph
}
Üres számítási kifejezés írása a számítási kifejezéskészítő Zero
metódusának hívását eredményezi.
Ez egy természetesebb szintaxis a korábban elérhető builder { () }
képest.
A hash utasítások nem sztring argumentumokat is használhatnak
A fordító hash direktívái korábban csak idézőjelekben átadott karakterlánc argumentumokat engedélyeztek. Most bármilyen típusú argumentumot használhatnak.
Korábban a következő volt:
#nowarn "0070"
#time "on"
Most a következőt írhatja:
#nowarn 0070
#time on
Ez a következő két változáshoz is kapcsolódik.
Kiterjesztett #help irányelv az fsi-ben a dokumentáció megjelenítéséhez a REPL-ben
Az F# Interactive #help
irányelve mostantól egy adott objektum vagy függvény dokumentációját jeleníti meg, amelyet mostantól idézőjelek nélkül is átadhat.
> #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
További részletekért lásd #help fejlesztése az F# interaktív blogbejegyzésében.
A figyelmeztetések letiltásához tegye lehetővé, hogy a #nowarn támogassa a hibakódok FS-előtagját.
Korábban, amikor le akart tiltani egy figyelmeztetést, és a #nowarn "FS0057"
-t írt, akkor Invalid warning number 'FS0057'
-et kapott. Annak ellenére, hogy a figyelmeztetési szám helyes, nem kellett volna FS
előtaggal rendelkeznie.
Most nem kell azzal töltenie az időt, hogy ezt kitalálja, mert a figyelmeztetési számok még az előtaggal együtt is el lesznek fogadva.
Ezek mindegyike működni fog:
#nowarn 57
#nowarn 0057
#nowarn FS0057
#nowarn "57"
#nowarn "0057"
#nowarn "FS0057"
Érdemes ugyanazt a stílust használni a projekt során.
Figyelmeztetés a TailCall attribútumról nem rekurzív függvényeken vagy let-bound értékeken
Az F# figyelmeztetést ad ki, amikor a [<TailCall>]
attribútumot olyan helyre helyezi, amelyhez nem tartozik. Bár nincs hatása a kódra, megzavarhatja, ha valaki elolvassa.
Ezek a használatok például figyelmeztetést adnak ki:
[<TailCall>]
let someNonRecFun x = x + x
[<TailCall>]
let someX = 23
[<TailCall>]
let rec someRecLetBoundValue = nameof(someRecLetBoundValue)
Attribútum-célok kényszerítése
A fordító mostantól helyesen alkalmazza a "AttributeTargets
" megkötést a let értékekre, függvényekre, összetett esetek deklarációira, implicit konstruktorokra, szerkezetekre és osztályokra. Ez megakadályozhat néhány nehezen észlelhető hibát, például elfelejtheti hozzáadni az egységargumentumot egy Xunit-teszthez.
Korábban a következőt írhatta:
[<Fact>]
let ``this test always fails`` =
Assert.True(false)
Amikor lefuttatta a teszteket dotnet test
, azok átmennek. Mivel a tesztfüggvény nem is valójában függvény, a tesztfuttató figyelmen kívül hagyta.
Most a megfelelő attribútumkényszerítéssel egy error FS0842: This attribute is not valid for use on this language element
-t fog kapni.
A standardkönyvtár (FSharp.Core) frissítései
Véletlen függvények gyűjteményekhez
A List
, Array
és Seq
modulok új funkciókkal rendelkeznek a véletlenszerű mintavételezéshez és a keveréshez. Ez megkönnyíti az F# használatát a gyakori adatelemzéshez, gépi tanuláshoz, játékfejlesztéshez és más olyan forgatókönyvekhez, ahol véletlenszerűségre van szükség.
Minden függvény a következő variánsokkal rendelkezik:
- Implicit, szálbiztos, megosztott Random példányt használó entitás.
- Az egyik, amely argumentumként egy
Random
-példányt használ - Egyéni
randomizer
függvényt használó függvény, amelynek 0,0-nál nagyobb vagy 1,0-nál kisebb lebegőpontos értéket kell visszaadnia
Négy függvény érhető el (mindegyik három változattal): Shuffle
, Choice
, Choices
és Sample
.
Keverés
A Shuffle
függvények egy azonos típusú és méretű új gyűjteményt ad vissza, amelyben minden elem véletlenszerűen vegyes helyzetben van. Az esély arra, hogy bármilyen pozícióba kerüljön, a gyűjtemény hosszán egyenletesen oszlik el.
let allPlayers = [ "Alice"; "Bob"; "Charlie"; "Dave" ]
let round1Order = allPlayers |> List.randomShuffle // [ "Charlie"; "Dave"; "Alice"; "Bob" ]
Tömbök esetén InPlace
változatok is léteznek, amelyek a meglévő tömb elemeit elkeverik ahelyett, hogy újat hoznak létre.
Választás
A Choice
függvények egyetlen véletlenszerű elemet adnak vissza az adott gyűjteményből. A véletlenszerű választás súlyozása egyenletesen történik a gyűjtemény méretén.
let allPlayers = [ "Alice"; "Bob"; "Charlie"; "Dave" ]
let randomPlayer = allPlayers |> List.randomChoice // "Charlie"
Választás
A Choices
függvények véletlenszerű sorrendben választják ki az N elemeket a bemeneti gyűjteményből, így az elemek többször is kiválaszthatók.
let weather = [ "Raining"; "Sunny"; "Snowing"; "Windy" ]
let forecastForNext3Days = weather |> List.randomChoices 3 // [ "Windy"; "Snowing"; "Windy" ]
Minta
A Sample
függvények véletlenszerű sorrendben választják ki az N elemeket a bemeneti gyűjteményből anélkül, hogy lehetővé tennék az elemek többszöri kijelölését. N nem lehet nagyobb a gyűjtemény hosszánál.
let foods = [ "Apple"; "Banana"; "Carrot"; "Donut"; "Egg" ]
let today'sMenu = foods |> List.randomSample 3 // [ "Donut"; "Apple"; "Egg" ]
A függvények és azok változatainak teljes listáját lásd: (RFC #1135).
Paraméter nélküli konstruktor CustomOperationAttribute
Ez a konstruktor megkönnyíti egy egyéni művelet létrehozását egy számítási kifejezésszerkesztő számára. A metódus nevét használja ahelyett, hogy explicit nevet kellene adnia (ha a legtöbb esetben a név már megegyezik a metódus nevével).
type FooBuilder() =
[<CustomOperation>] // Previously had to be [<CustomOperation("bar")>]
member _.bar(state) = state
C#-gyűjteménykifejezés támogatása F#-listákhoz és -csoportokhoz
Ha F#-listákat és -csoportokat használ a C#-ból, mostantól gyűjteménykifejezésekkel inicializálhatja őket.
Ahelyett, hogy:
FSharpSet<int> mySet = SetModule.FromArray([1, 2, 3]);
Most már írhat:
FSharpSet<int> mySet = [ 1, 2, 3 ];
A gyűjteménykifejezések megkönnyítik a C#-ból származó F# nem módosítható gyűjtemények használatát. Érdemes F# gyűjteményeket használni, ha szüksége van a szerkezeti egyenlőségre, amellyel a System.Collections.Immutable gyűjtemények nem rendelkeznek.
Fejlesztői hatékonyságnövelő fejlesztések
Elemző helyreállítása
Folyamatos fejlesztések történtek a parser helyreállításában, ami azt jelenti, hogy az eszközök (például a szintaxiskiemelés) továbbra is működnek a kóddal, amikor éppen szerkeszti, és lehet, hogy nem mindig szintaktikailag helyes.
Az elemző például helyreállítja a befejezetlen as
mintákat, objektumkifejezéseket, enum esetdeklarációkat, rekorddeklarációkat, összetett elsődleges konstruktormintákat, megoldatlan hosszú azonosítókat, üres egyezési záradékokat, hiányzó egyesítő esetmezőket és hiányzó egyesítő esetmezőtípusokat.
Diagnosztika
A diagnosztika vagy annak megértése, hogy a fordító mit nem szeret a kódban, fontos része az F# felhasználói élményének. Az F# 9-ben számos új vagy továbbfejlesztett diagnosztikai üzenet vagy pontosabb diagnosztikai hely található.
Ezek a következők:
- Nem egyértelmű felülbírálási módszer az objektumkifejezésben
- Absztrakt tagok, ha nem absztrakt osztályokban használják
- Olyan tulajdonság, amelynek neve megegyezik egy megkülönböztetett unió eset nevével
- Az aktív minta argumentumainak száma nem egyezik
- Ismétlődő mezőkkel rendelkező uniók
- A
use!
és aand!
használata számítási kifejezésekben
A létrehozott ILtöbb mint 65 520 metódussal rendelkező osztályok esetében is új fordítási időhiba lépett fel. Az ilyen osztályok nem tölthetők be a CLR által, és futásidejű hibát eredményeznek. (Nem fog ennyi metódust létrehozni, de előfordultak már esetek generált kóddal.)
Valós láthatóság
Van egy furcsa, hogyan F# generál szerelvényeket, amelyek eredményeként a magántagok írása IL belső. Ez lehetővé teszi a nem F#-projektekből történő nem megfelelő hozzáférést privát tagokhoz egy F# projektben, amelyhez InternalsVisibleTo
keresztül férnek hozzá.
Most a --realsig+
fordítójelzőn keresztül elérhető a viselkedésre vonatkozó jóváhagyási javítás. Próbálja ki a megoldásban, és ellenőrizze, hogy a projektek bármelyike függ-e ettől a viselkedéstől. A következő módon adhat hozzá .fsproj
fájljaihoz:
<PropertyGroup>
<RealSig>true</RealSig>
</PropertyGroup>
Teljesítménybeli fejlesztések
Optimalizált egyenlőségi ellenőrzések
Az egyenlőségi ellenőrzések most már gyorsabbak, és kevesebb memóriát foglalnak le.
Például:
[<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
Az érintett tömbfüggvények 2 tagú szerkezetre alkalmazott teljesítménymérési eredményei
Előtt:
Módszer | Jelent | Hiba | Gen0 | Kiosztott |
---|---|---|---|---|
TömbTartalmazLétező | 15,48 ns | 0,398 ns | 0.0008 | 48 B |
TömbTartalmazNemlétezőt | 5190,95 ns | 103,533 ns | 0.3891 | 24000 B |
ArrayLétezikLétező | 17,97 ns | 0,389 ns | 0.0012 | 72 B |
TömbLétezikNemlétező | 5 316,64 ns | 103,776 ns | 0.3891 | 24024 B |
TömbMegpróbáljaMeglévőtKeresni | 24,80 ns | 0,554 ns | 0.0015 | 96 B |
ArrayTryFindNonexisting | 5139,58 ns | 260,949 ns | 0.3891 | 24024 B |
ArrayTryFindIndexExisting | 15,92 ns | 0,526 ns | 0.0015 | 96 B |
ArrayTryFindIndexNonexisting | 4 349,13 ns | 100,750 ns | 0.3891 | 24024 B |
Után:
Módszer | Jelentés | Hiba | Generáció 0 | Kiosztott |
---|---|---|---|---|
TömbTartalmazLévőt | 4,865 ns | 0,3452 ns | - | - |
TömbNemLétezőElemetTartalmaz | 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 | 1140,515 ns | 22,7372 ns | - | 24 B |
ArrayTryFindIndexExisting | 14,864 ns | 0,3648 ns | 0.0008 | 48 B |
ArrayTryFindIndexNonexisting | 990,028 ns | 19,7157 ns | - | 24 B |
Az összes részletet itt olvashatja el: F# Fejlesztői történetek: Hogyan javítottuk végre a 9 éves teljesítményproblémát.
Területmegosztás a diszkriminált szakszervezetek számára
Ha a strukturált diszkriminált unió egyik vagy másik esetben lévő mezők ugyanazzal a névvel és típussal rendelkeznek, azonos memóriaterületet oszthatnak meg, csökkentve ezzel a szerkezet memóriaigényét. (Korábban ugyanezek a mezőnevek nem voltak engedélyezve, így a bináris kompatibilitással nem volt probléma.)
Például:
[<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
Összehasonlítva az előző ponthoz (ahol egyedi mezőneveket kellett használnia):
[<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
Integrált tartományoptimalizálások
A fordító mostantól optimalizált kódot hoz létre a start..finish
és start..step..finish
kifejezések további példányaihoz. Korábban ezek csak akkor lettek optimalizálva, amikor a típus int
/int32
volt, és a lépés állandó 1
vagy -1
volt. Más integráltípusok és különböző lépésértékek nem hatékony IEnumerable
-alapú implementációt használtak. Ezek mindegyike optimalizálva van.
Ez 1,25-szöröstől akár 8-szoros sebességnövekedést eredményez a hurkokban.
for … in start..finish do …
Lista-/tömbkifejezések:
[start..step..finish]
és megértések:
[for n in start..finish -> f n]
Optimalizált for x in xs -> …
lista- és tömbértelmezésekben
Kapcsolódóan, a for x in xs -> …
kifejezések listákhoz és tömbökhöz való optimalizálása megtörtént, különösen a tömbök esetében jelentős fejlesztésekkel, akár 10× gyorsulással és 1/3–1/4 memóriamérettel.
Az eszközkezelés fejlesztései
Élő pufferek a Visual Studióban
Ezt a korábban választható funkciót alaposan teszteltük, és alapértelmezés szerint engedélyezve van. Az IDE-t futtató háttérfordító mostantól élő fájlpufferekkel működik, ami azt jelenti, hogy a módosítások alkalmazásához nem kell a fájlokat lemezre mentenie. Korábban ez váratlan viselkedést okozhat. (Leghírhedtebb, amikor egy szerkesztett, de nem mentett fájlban lévő szimbólumot próbált átnevezni.)
Elemző és kódjavítás a szükségtelen zárójelek eltávolításához
Néha extra zárójeleket használnak az egyértelműség érdekében, de máskor csak zaj. Az utóbbi esetben most egy kódjavítást kap a Visual Studióban az eltávolításukhoz.
Például:
let f (x) = x // -> let f x = x
let _ = (2 * 2) + 3 // -> let _ = 2 * 2 + 3
Az F# egyéni vizualizációs támogatása a Visual Studióban
A Visual Studio hibakereső vizualizációja mostantól F#-projektekkel is működik.
"A feldolgozási folyamat közepén megjelenő aláírási eszköztippek"
Korábban nem kínáltak aláírási segítséget az alábbihoz hasonló helyzetekben, ahol egy folyamat közepén lévő függvény már alkalmazott egy összetett, curried paramétert (például egy lambdát). Ekkor megjelenik az aláírás elemleírása a következő paraméterhez (state
):