Novinky v F# 10

F# 10 přináší několik vylepšení jazyka F#, knihovny FSharp.Core a nástrojů. Tato verze je vylepšená verze zaměřená na přehlednost, konzistenci a výkon, a to s malými, ale smysluplnými vylepšeními, díky kterým je váš každodenní kód čitelnější a robustnější. F# 10 se dodává s .NET 10 a sadou Visual Studio 2026.

Nejnovější sadu .NET SDK si můžete stáhnout ze stránky pro stahování .NET.

Začínáme

F# 10 je k dispozici ve všech distribucích .NET Core a nástrojích sady Visual Studio. Další informace najdete v tématu Začínáme s jazykem F#.

Potlačení upozornění s vymezeným oborem

Pomocí nové #warnon direktivy teď můžete potlačit upozornění v konkrétních částech kódu. Tato kombinace se stávající #nowarn direktivou vám poskytne přesnou kontrolu nad tím, která upozornění platí tam.

Dříve, když jste použili #nowarn, zakázalo upozornění pro zbytek souboru, což mohlo potlačit legitimní problémy jinde. Podívejme se na motivující příklad:

// We know f is never called with None.
let f (Some a) =    // creates warning 25, which we want to suppress
    // 2000 loc, where the incomplete match warning is beneficial

Pokud přidáte #nowarn 25 nad definici funkce, zakáže FS0025 pro celý zbytek souboru.

S F# 10 teď můžete označit přesný oddíl, ve kterém chcete upozornění potlačit:

#nowarn 25
let f (Some x) =    // FS0025 suppressed
#warnon 25
    // FS0025 enabled again

Naopak pokud je upozornění globálně zakázané (například prostřednictvím příznaku kompilátoru), můžete ho povolit místně pomocí #warnon. Tato direktiva se použije až do odpovídajícího #nowarn nebo konce souboru.

Důležité poznámky k kompatibilitě:

Tato funkce obsahuje několik změn, které zlepšují konzistenci #nowarn/#warnon direktiv. Jedná se o zásadní změny:

  • Kompilátor už neumožňuje víceřádkové a prázdné direktivy warn.
  • Kompilátor již neumožňuje prázdné znaky mezi # a nowarn.
  • Pro čísla upozornění nelze použít trojité, interpolované nebo doslovné řetězce.

Chování skriptu se také změnilo. Dříve, když jste přidali direktivu #nowarn kdekoli ve skriptu, použila se na celou kompilaci. Nyní je jeho chování ve skriptech stejné jako v souborech .fs, platí pouze do konce souboru nebo odpovídajícího #warnon.

Tato funkce implementuje RFC FS-1146.

Modifikátory přístupu u automatických přístupových objektů vlastností

Běžným vzorem v objektově orientovaném programování je vytvoření veřejně čitelného, ale soukromě proměnlivého stavu. Před F# 10 jste potřebovali explicitní syntaxi vlastností s backingovými poli (skryté proměnné, které ukládají skutečné hodnoty vlastností), abyste toho dosáhli, což přidalo opakující se kód:

type Ledger() =
    [<DefaultValue>] val mutable private _Balance: decimal
    member this.Balance with public get() = this._Balance and private set v = this._Balance <- v

S jazykem F# 10 teď můžete u jednotlivých přístupových objektů použít různé modifikátory přístupu. To vám umožní určit různé úrovně přístupu pro getter a setter u vlastnosti, což zjednodušuje tento vzor.

type Ledger() =
    member val Balance = 0m with public get, private set

Modifikátor přístupu můžete umístit buď před název vlastnosti (platí pro oba přístupové objekty), nebo před jednotlivé přístupové objekty, ale ne oba současně.

Všimněte si, že tato funkce se nevztahuje na soubory podpisu (.fsi). Správný podpis pro výše uvedený Ledger příklad je:

type Ledger() =
    member Balance : decimal
    member private Balance : decimal with set

Tato funkce implementuje RFC FS-1141.

Volitelné parametry ValueOption

Pro ValueOption<'T> teď můžete použít reprezentaci založenou na struktuře. Když použijete [<Struct>] atribut na volitelný parametr, kompilátor použije ValueOption<'T> místo odkazového option typu. Tím se zabrání přidělení haldy (paměť přidělená na spravovanou haldu, která vyžaduje uvolňování paměti) pro obálku možností, což je výhodné v kritickém kódu výkonu.

Dříve jazyk F# vždy používal typ přidělený option haldou pro volitelné parametry, i když parametr chybí:

// Prior to F# 10: always uses reference option
type X() =
    static member M(?x : string) =
        match x with
        | Some v -> printfn "Some %s" v
        | None -> printfn "None"

V jazyce F# 10 můžete pomocí atributu [<Struct>] využít atribut podporovaný strukturou: ValueOption

type X() =
    static member M([<Struct>] ?x : string) =
        match x with
        | ValueSome v -> printfn "ValueSome %s" v
        | ValueNone -> printfn "ValueNone"

Tím se eliminuje přidělení haldy, pokud argument chybí, což je výhodné v kritickém kódu výkonu.

Tuto možnost založenou na struktuře vyberte pro malé hodnoty nebo často vytvořené typy, ve kterých záleží na alokačním tlaku. Použijte výchozí referenci option když spoléháte na existující nástroje pro porovnávání vzorů, potřebujete referenční sémantiku nebo když je rozdíl ve výkonu zanedbatelný. Tato funkce posiluje paritu s jinými konstrukcemi jazyka F#, které již podporují ValueOption.

Podpora koncových volání ve výpočetních výrazech

F# 10 obsahuje optimalizace tail-call pro výpočetní výrazy. Tvůrci výpočetních výrazů nyní mohou využít těchto optimalizací implementací speciálních metod.

Když kompilátor přeloží výpočetní výrazy do regulárního kódu jazyka F# (proces označovaný jako desugaring), rozpozná, kdy výraz jako return!, yield!nebo do! se zobrazí v koncové pozici. Pokud tvůrce poskytuje následující metody, kompilátor tato volání směruje na optimalizované vstupní body:

  • ReturnFromFinal - vyžadováno pro funkci 'tail' return! (návrat k ReturnFrom, pokud chybí)
  • YieldFromFinal - vyžadováno pro funkci 'tail' yield! (návrat k YieldFrom, pokud chybí)
  • Pro terminál do!dává kompilátor přednost ReturnFromFinal, pak YieldFromFinal, před návratem zpět do normální Bind cesty

Tyto *Final členy jsou volitelné a existují čistě pro povolení optimalizace. Konstruktory, které tyto členy neposkytnou, zachovávají stávající sémantiku beze změny.

Například:

coroutine {
    yield! subRoutine() // tail position -> YieldFromFinal if available
}

V neterminální pozici však:

coroutine {
    try
        yield! subRoutine() // not tail -> normal YieldFrom
    finally ()
}

Důležitá poznámka k kompatibilitě:

Tato změna může být zásadní, pokud tvůrce výpočetních výrazů už definuje členy s těmito názvy. Ve většině případů stávající tvůrci při kompilaci pomocí F# 10 nadále pracují bez úprav. Starší kompilátory budou nové *Final metody ignorovat, takže tvůrci, kteří musí zůstat kompatibilní s dřívějšími verzemi kompilátoru, by neměli předpokládat, že kompilátor tyto metody vyvolá.

Tato funkce implementuje RFC FS-1330.

Typové vazby ve výpočetních výrazech bez závorek

F# 10 odebere požadavek na závorky při přidávání poznámek typu do vazeb výpočetních výrazů. Nyní můžete přidávat poznámky k typům na let!, use!a and! vazby pomocí stejné syntaxe jako běžné let vazby.

Dříve jste museli pro psaní poznámek použít závorky:

async {
    let! (a: int) = fetchA()
    and! (b: int) = fetchB()
    use! (d: MyDisposable) = acquireAsync()
    return a + b
}

V F# 10 můžete psát poznámky bez závorek:

async {
    let! a: int = fetchA()
    and! b: int = fetchB()
    use! d: MyDisposable = acquireAsync()
    return a + b
}

Povolit _ ve use! vazbách

V rámci výpočetních výrazů teď můžete ve vazbách _ použít vzorek ignorování (use!). Tím se zarovná chování use! s běžnými use vazbami.

Dříve kompilátor odmítal vzor zahození ve use! vazbách a nutil vás vytvářet dočasné identifikátory.

counterDisposable {
    use! _ignored = new Disposable()
    // logic
}

V F# 10 můžete použít vzor zahození přímo:

counterDisposable {
    use! _ = new Disposable()
    // logic
}

Tím se upřesní záměr při vytváření vazby asynchronních prostředků, jejichž hodnoty jsou potřeba pouze pro správu životnosti.

Odmítnutí pseudovnořených modulů v typech

Kompilátor teď vyvolá chybu, když umístíte module deklaraci odsazenou na stejné strukturové úrovni uvnitř definice typu. Tím se zpřísní strukturální ověřování, aby odmítlo zavádějící umístění modulů v rámci typů.

Dříve kompilátor akceptoval module deklarace odsazené v definicích typů, ale ve skutečnosti byly tyto moduly vytvořeny jako sourozenci typu, nikoli vnořeny do něj.

type U =
    | A
    | B
    module M = // Silently created a sibling module, not nested
        let f () = ()

U F# 10 tento vzor vyvolá chybu FS0058, což vás přinutí objasnit váš záměr správným umístěním modulu:

type U =
    | A
    | B

module M =
    let f () = ()

Upozornění na vyřazení z důvodu vynechání seq

Kompilátor vás teď upozorní na jednoduché sekvenční výrazy, které vynechají seq tvůrce. Když používáte samotné složené závorky jako { 1..10 }, zobrazí se upozornění na jejich vyřazení, které vás vyzve k použití explicitní podoby seq { ... }.

Historicky F# umožňovalo speciální syntaxi "jednoduchého porozumění sekvenci", kde jste mohli vynechat klíčové slovo seq.

{ 1..10 } |> List.ofSeq  // implicit sequence, warning FS3873 in F# 10

V F# 10 kompilátor varuje o tomto vzoru a podporuje explicitní formulář:

seq { 1..10 } |> List.ofSeq

Jedná se o upozornění, nikoli o chybu, která vám dává čas na aktualizaci základu kódu. Pokud chcete toto upozornění potlačit, použijte NoWarn vlastnost v souboru projektu nebo #nowarn direktivě místně a předejte ji číslo upozornění: 3873.

Explicitní seq formulář zlepšuje srozumitelnost a konzistenci kódu s jinými výpočetními výrazy. Budoucí verze jazyka F# můžou tuto chybu provést, proto při aktualizaci kódu doporučujeme použít explicitní syntaxi.

Tato funkce implementuje RFC FS-1033.

Vynucení cílového atributu

F# 10 vynucuje ověření cíle atributů ve všech konstruktorech jazyka. Kompilátor nyní ověřuje, že atributy jsou použity pouze na jejich zamýšlené cíle tím, že kontroluje přiřazené pomocí 'let' hodnoty, funkce, sjednocovací případy, implicitní konstruktory, struktury a třídy.

Dříve kompilátor bezobslužně umožňoval chybně použít atributy pro nekompatibilní cíle. To způsobilo drobné chyby, jako jsou ignorování atributů testů, když jste zapomněli () vytvořit funkci:

[<Fact>]
let ``this is not a function`` = // Silently ignored in F# 9, not a test!
    Assert.True(false)

V jazyce F# 10 kompilátor vynucuje cíle atributů a při nesprávném použití atributů vyvolá upozornění:

[<Fact>]
//^^^^ - warning FS0842: This attribute cannot be applied to property, field, return value. Valid targets are: method
let ``this is not a function`` =
    Assert.True(false)

Důležitá poznámka k kompatibilitě:

Jedná se o zásadní změnu, která může odhalit dříve tiché problémy v základu kódu. Časné chyby brání problémům se zjišťováním testů a zaručují, že atributy, jako jsou analyzátory a dekorátory, fungují podle očekávání.

Podpora pro and! ve výrazech v úlohách

Nyní můžete čekat na více úloh současně pomocí and! v výrazech úloh. Použití task je oblíbený způsob práce s asynchronními pracovními postupy v jazyce F#, zejména pokud potřebujete interoperabilitu s jazykem C#. Do této chvíle však ve výpočetním výrazu neexistoval žádný stručný způsob, jak souběžně očekávat více úkolů.

Možná jste začali s kódem, který čekal na výpočty postupně.

// Awaiting sequentially
task {
    let! a = fetchA()
    let! b = fetchB()
    return combineAB a b
}

Pokud byste je pak chtěli změnit tak, aby se čekalo na ně souběžně, obvykle byste použili Task.WhenAll:

// Use explicit Task combinator to await concurrently
task {
    let ta = fetchA()
    let tb = fetchB()
    let! results = Task.WhenAll([| ta; tb |])
    return combineAB ta.Result tb.Result
}

V jazyce F# 10 můžete použít and! pro idiomatičtější přístup:

task {
    let! a = fetchA()
    and! b = fetchB()
    return combineAB a b
}

To kombinuje sémantiku souběžné verze s jednoduchostí sekvenční verze.

Tato funkce implementuje návrh jazyka F# #1363 a implementuje se jako doplněk knihovny FSharp.Core . Většina projektů získá nejnovější verzi FSharp.Core automaticky z kompilátoru, pokud verzi explicitně neuzamknou. V takovém případě ji budete muset aktualizovat, aby používala tuto funkci.

Lepší oříznutí ve výchozím nastavení

F# 10 odstraní dlouhodobý problém s optimalizací sestavení jazyka F#. Oříznutí je proces odebrání nepoužívaného kódu z publikované aplikace, aby se zmenšila jeho velikost. Už nemusíte ručně udržovat ILLink.Substitutions.xml soubor jen kvůli odstranění velkých blobů metadatových zdrojů F# (data podpisu a optimalizace, která kompilátor používá, ale vaše aplikace během běhu nepotřebuje).

Když publikujete s povoleným oříznutím (PublishTrimmed=true), sestavení jazyka F# nyní automaticky generuje integrovaný soubor nahrazení, který cílí na tyto výhradně nástrojové prostředky jazyka F#.

Dříve jste museli tento soubor udržovat ručně, abyste mohli odstranit metadata. To přidalo nároky na údržbu a bylo snadné zapomenout.

Výsledkem je menší výstup ve výchozím nastavení, méně opakující se kód k údržbě, a menší nebezpečí problémů s údržbou. Pokud potřebujete úplné ruční řízení, můžete stále přidat vlastní soubor nahrazení. Automatické generování můžete vypnout pomocí vlastnosti <DisableILLinkSubstitutions>false</DisableILLinkSubstitutions>.

Paralelní kompilace ve verzi Preview

Vzrušující aktualizace pro uživatele jazyka F#, kteří chtějí zkrátit dobu kompilace: funkce paralelní kompilace se stabilizují. Počínaje rozhraním .NET 10 jsou tři funkce - kontrola typů založená na grafech, paralelní generování IL kódu a paralelní optimalizace - seskupeny společně pod ParallelCompilation vlastnost projektu.

F# 10 umožňuje toto nastavení ve výchozím nastavení pro projekty používající LangVersion=Preview. Plánujeme ji povolit pro všechny projekty v .NET 11.

Nezapomeňte to vyzkoušet a zjistit, jestli urychlí kompilaci. Povolení paralelní kompilace v F# 10:

<PropertyGroup>
    <ParallelCompilation>true</ParallelCompilation>
    <Deterministic>false</Deterministic> <!-- Note: deterministic builds don't get the benefits of parallel compilation -->
</PropertyGroup>

Pokud se chcete odhlásit, i když si stále užíváte další funkce ve verzi Preview, nastavte ParallelCompilation na hodnotu false:

<PropertyGroup>
    <LangVersion>Preview</LangVersion>
    <ParallelCompilation>false</ParallelCompilation>
</PropertyGroup>

Paralelní kompilace může výrazně zkrátit dobu kompilace u projektů s více soubory a závislostmi.

Mezipaměť podsoučtů typů

Kompilátor teď ukládá kontroly relací typů do mezipaměti, aby urychlil odvozování typů a zlepšil výkon integrovaného vývojového prostředí, zejména při práci s komplexními hierarchiemi typů. Uložením a opětovným použitím výsledků z předchozích subsumpčních kontrol se kompilátor vyhne zbytečným výpočtům, které dříve zpomalovaly kompilaci a IntelliSense.

Správa mezipaměti:

Ve většině případů mezipaměť podtypového začlenění zlepšuje výkon bez jakékoli konfigurace. Pokud se ale setkáte se zvýšenými nároky na paměť nebo zvýšením využití procesoru (kvůli pracovníkům údržby mezipaměti), můžete upravit chování mezipaměti:

  • Pokud chcete mezipaměť úplně zakázat, nastavte <LangVersion>9</LangVersion> v souboru projektu, aby se vrátila do chování F# 9.
  • Pokud chcete vypnout asynchronní vyřazení mezipaměti (což zvyšuje zátěž vlákna) a místo toho použít synchronní vyřazení, nastavte proměnnou prostředí FSharp_CacheEvictionImmediate=1.