Apa yang baru di F# 11

Selamat datang di F# 11! Rilis ini membantu Anda menjaga model domain tetap berbeda, menggunakan kembali algoritma generik, mengoordinasikan pekerjaan asinkron, dan mengurangi penggunaan memori kompilator.

Terima kasih kepada komunitas F# atas proposal, implementasi, ulasan, dan pengujian di balik perubahan ini. Tautan kontributor menyertai fitur di bawah ini.

Note

Contohnya memerlukan kompilator F# 11 dan FSharp.Core 11 dengan fitur yang ditampilkan. Dalam SDK .NET 11.0.100, fitur F# 11.2 memerlukan <LangVersion>preview</LangVersion>; lihat pengaturan bahasa dan dukungan SDK yang direncanakan. Contoh runtime-async memiliki persyaratan runtime tambahan; SDK .NET 11 sebelumnya saja mungkin tidak berisi setiap perubahan.

Sebaran data: model yang berbeda tanpa pemetaan berulang

Untuk menjaga model penyimpanan dan aplikasi tetap berbeda, spread record menggunakan kembali field bersama mereka tanpa deklarasi berulang. Di sini model berbagi detail pelanggan tetapi menggunakan jenis pengidentifikasi yang berbeda:

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 menambahkan deklarasi field. Di batas, ...row menyalin nilai saat konversi mengurai pengidentifikasi:

let toCustomer (row: CustomerRow) =
    match Guid.TryParse row.Id with
    | true, id -> Ok ({ ...row; Id = CustomerId id }: Customer)
    | false, _ -> Error "Invalid customer identifier."

Tujuan menyimpan detail bersama, menggantikan Id, dan mengecualikan InternalNote. Guid.TryParse, bukan spread, memvalidasi pengidentifikasi; input cacat menghasilkan Error.

Ini mengatasi masalah pemetaan lapisan aplikasi. Nilai spread juga menggabungkan record bernama dan anonim; jenis spread hanya membuat record bernama. Field yang muncul belakangan akan diprioritaskan, dan sumber berjalan sekali secara berurutan. Lihat operator spread record untuk contoh komposisi dan risiko evolusi bersama.

Terima kasih kepada @brianrourkeboll atas spread rekor.

Anggota ekstensi untuk algoritma generik, operator, dan tuple

Untuk berbagi algoritma di seluruh tipe yang sudah ada, F# 11.2 memungkinkan anggota perluasan memenuhi batasan parameter tipe yang diselesaikan secara statis (SRTP). Misalnya, satu transformasi dapat menangani daftar dan opsi tanpa tipe pembungkus.

Note

Dalam SDK .NET 11.0.100, aktifkan <LangVersion>preview</LangVersion> di tempat ekstensi di-resolve dan buka modulnya. Lihat persyaratan library.

Terapkan transformasi di seluruh daftar dan opsi

Dengan pustaka map dalam cakupan, aplikasi dapat membersihkan salah satu dari kedua representasi, mengubah jenis elemen, dan menyusun transformasi:

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]

Adaptor dalam Structural.Adapters menggunakan operasi yang sudah dikenal, daripada memaksakan kontainer baru:

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

Fungsi bersama dalam Structural.Algorithms meminta jenis input untuk anggota statis Map :

let inline map (mapping: 'T -> 'U) (source: ^Input) : ^Output =
    (^Input :
        (static member Map: ^Input * ('T -> 'U) -> ^Output)
        (source, mapping))

^Input dan ^Output merupakan tipe yang diselesaikan secara statis. Pada setiap panggilan konkret, pengkompilasi memilih anggota dengan signatur yang diperlukan. Ekstensi tidak menambahkan implementasi antarmuka. Aplikasi lengkap membuka kedua modul.

Operator, konteks, dan jenis output

Mekanisme yang sama mendukung operator tuple dan penyebaran konteks dan konstruksi yang diarahkan output. header tuple native memerlukan fitur F# 11.2 diaktifkan pada deklarasinya serta di lokasi pemanggilan penyelesaian ekstensi.

Tambahan terpisah , AllowOverloadOnReturnType, memungkinkan satu pabrik mendeklarasikan kelebihan beban yang dibedakan oleh pengembaliannya:

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"

Atribut Core 11 ini tidak memerlukan pratinjau. Anotasi hasil memilih kelebihan beban; tanpa itu panggilan tetap ambigu. Lihat Pilih berdasarkan tipe pengembalian.

Terima kasih kepada @gusty karena telah memperluas dukungan pemrograman generik.

integrasi .NET dan pengalaman pengembangan

Delegasi langsung mempertahankan kontrak asli handler

Untuk mengekspos nama handler dan atribut pengikatan ke kerangka kerja, F# 11 dapat menargetkan fungsi bernama yang kompatibel secara langsung alih-alih pembungkus. ASP.NET Core API Minimal kemudian dapat membaca rute, layanan, dan kontrak header:

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)

Lihat proyek yang dapat dijalankan dan aturan konstruksi/kompatibilitas. Pengambilan dan adaptasi argumen masih memerlukan pembungkus.

Terima kasih kepada @kerams untuk delegasi langsung.

Menggunakan kembali dokumentasi XML

Untuk mempertahankan komentar API bersama sekali, tempatkan di shared.xml:

<docs>
  <tracking>
    <remarks>An unrecognized tracking identifier has status Unknown.</remarks>
  </tracking>
</docs>

Dengan GenerateDocumentationFile diaktifkan, F# 11 meluas <include> ke entri anggota yang dipancarkan:

/// <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)

Termasuk implementasi memperbarui kedua deskripsi dari satu fragmen. Secara terpisah, pemecah masalah alat mengekspos deskripsi dasar untuk <inheritdoc/>; pengkompilasi meninggalkan tag tersebut untuk konsumen dokumentasi. Lihat dokumentasi XML untuk contoh dan aturan resolusi.

Berkat @T-Gro untuk dokumentasi XML bersama di FSharp.Core.

Men-debug fungsi sebaris dalam build yang tidak dioptimalkan

Untuk memeriksa argumen pembantu sebaris, atur titik henti di dalam build yang tidak dioptimalkan let total dan masuk ke addFee. F# 11 sekarang lebih suka metode yang dapat dipanggil dalam build ini:

let inline addFee (fee: decimal) (amount: decimal) =
    let total = amount + fee
    total

let invoiceTotal = addFee 2m 40m
printfn "%M" invoiceTotal
// 42

Build yang dioptimalkan mempertahankan inlining. Beberapa konstruksi masih harus diperluas. Lihat Men-debug fungsi sebaris untuk batas dan kontrol tersebut --always-inline .

Async dan koordinasi tugas

Untuk menyambungkan alur kerja F# dengan API berbasis tugas, FSharp.Core 11 menambahkan bridge yang mendukung pembatalan, batch terbatas, dan transformasi hasil.

Terikat I/O yang luar biasa, bukan hanya koleksi hasil

Batch pembacaan yang besar dapat memulai lebih banyak tugas daripada jumlah tugas yang seharusnya masih tertunda pada suatu aplikasi. Untuk memutuskan kapan pekerjaan berbasis tugas dimulai, koordinator membutuhkan pabrik, bukan tugas yang telah dimulai:

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

Paling banyak empat bacaan berjalan secara bersamaan; hasil mempertahankan urutan input. Kegagalan meminta pembatalan terkait melalui token yang disediakan. Ini membatasi jumlah permintaan yang diproses secara bersamaan, bukan jumlah permintaan per detik. Lihat Fungsi asinkron dan tugas untuk varian berurutan dan hasil unit.

Pertahankan pembatalan dan pengecualian yang mendasarinya di seluruh batas

Saat alur kerja memulai pekerjaan eksternal, tokennya harus ikut terbawa bersama pekerjaan tersebut. Async.StartTaskImmediate meneruskan token alur kerja yang sedang berjalan ke task factory dan menunggu penyelesaian:

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
    }

Kegagalan satu tugas diteruskan ke handler ini sebagai penyebab dasarnya FileNotFoundException. Untuk tugas atau tugas nilai yang sudah dibuat, gunakan Async.Await; Async.AwaitTask yang lebih lama mempertahankan perilaku pembungkus pengecualiannya.

Untuk pemanggil berbasis tugas, Task.startAsyncImmediate token workflow menyediakan jembatan balik. Ini dimulai pada utas panggilan sampai suspensi asinkron pertama.

Gunakan fungsi pembantu yang sesuai untuk transformasi sederhana

Untuk mengubah satu hasil tanpa ekspresi komputasi penuh, gunakan pembantu yang cocok di Microsoft.FSharp.Control. Misalnya, baca sebuah file, dapatkan panjangnya, dan kembalikan kegagalan biasa sebagai Error:

open System.IO
open System.Threading

let fileLength (path: string) (ct: CancellationToken) =
    File.ReadAllTextAsync(path, ct)
    |> Task.map String.length
    |> Task.catch

Sebuah file yang berisi Report 1 menghasilkan Ok 8; file yang hilang menghasilkan Error yang berisi FileNotFoundException. Pembatalan masih diteruskan. Async dan ValueTask menawarkan pencocokan map, bind, dan pembantu kesalahan; lihat Fungsi asinkron dan tugas. Gunakan ekspresi komputasi untuk cabang atau beberapa nilai dependen.

Fungsi pembantu ValueTask memerlukan build Core netstandard2.1 atau net10.0 dan penggunaan sekali pakai.

Terima kasih kepada @bartelink atas kontribusi API asinkron/tugas.

Munculkan kembali setelah pelaporan kegagalan asinkron

Pencatatan operasional harus menambahkan informasi tanpa mengubah pengecualian yang ditangkap pemanggil atau menyembunyikan tempat kegagalan bermula. Handler ekspresi komputasi kini dapat menggunakan reraise () bahkan setelah mereka menunggu pencatat:

Note

Rethrow pada computation expression adalah fitur F# 11.2. Di SDK .NET 11.0.100, aktifkan <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 ()
    }

Setelah pencatatan log berhasil, pengecualian yang sama dan informasi lokasi asli pelemparan disebarluaskan. Kegagalan logger dapat menggantikan yang asli. Ini menangani permintaan observability async tanpa async runtime. Lihat Rethrow dari ekspresi komputasi.

Terima kasih untuk @xperiandri atas dukungan ini.

FSharp.Core dapat dibangun berdasarkan .NET modern

Untuk menggunakan API modern tanpa menghilangkan target yang lebih lama, FSharp.Core menambahkan net10.0 bersama netstandard2.0 dan netstandard2.1. NuGet memilih build yang kompatibel. Pengemasan tool yang diperbarui juga memungkinkan pelepasan sumber daya asinkron berbasis tugas di F# Interactive, seperti dalam proyek yang dikompilasi. Lihat kerangka kerja yang ditargetkan FSharp.Core.

Terima kasih untuk @T-Gropenargetan Core modern dan kemasan alat SDK.

Asinkron runtime untuk penulis builder

Penulis penyusun dapat membiarkan runtime menangguhkan dan melanjutkan metode yang ditandai alih-alih menggunakan mesin status yang dihasilkan kompiler. Builder eksperimental yang didefinisikan pustaka mengekspos mekanisme sebagai ekspresi komputasi:

let readManifest (path: string) =
    runtimeTask {
        let! text = System.IO.File.ReadAllTextAsync path
        return text.Length
    }

Note

Penyusun eksperimental ini menggunakan fitur F# 11.2 di mana implementasi sebarisnya ditentukan atau diperluas; di .NET 11.0.100 SDK, aktifkan pratinjau. Ini juga memerlukan build dan referensi milik Core 11 net10.0 dan runtime dengan dukungan runtime-async. Kombinasi runtime/referensi terverifikasi di sini adalah .NET 11 RC1. Menargetkan .NET 10 saja tidak cukup. Lihat Eksperimen asinkron runtime.

FSharp.Core tidak menyertakan sampel ini sebagai builder tujuan umum. Kode task dan async biasa tidak secara otomatis beralih ke mode tersebut, dan pemanggil API biasa yang sudah dikompilasi yang mengembalikan Task pada dasarnya juga tidak memerlukan fitur pratinjau.

Terima kasih kepada @majocha untuk runtime asinkron.

Teks ramah AOT asli dan array dua dimensi

Untuk mendukung Native AOT, F# 11 menghapus pemformatan berbasis refleksi dan persyaratan alokasi dinamis dari operasi teks dan array yang lebih umum.

Jika Anda sudah memiliki string, Anda tidak memerlukan format "%s" hanya untuk menuliskannya. Fungsi Inti baru print dan printn menerima nilai melalui konversi string milik F#; printn menambahkan baris baru:

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

Interpolasi string menggunakan konversi langsung dan String.Concat dalam lebih banyak kasus. .NET-format lubang seperti {total:F2} menggunakan pemformatan invarian dan kotak nilai; %A dan formulir cetak bantalan presisi masih memerlukan pemformatan cetak. Pilih kultur eksplisit untuk output yang dilokalkan.

Dengan --reflectionfree, record dan union juga memiliki output ToString yang dihasilkan. Lihat Interpolasi dan Native AOT untuk contoh kultur dan jalur yang didukung.

Berkat @bbatsov untuk fungsi cetak dan @charlesroddie untuk implementasi interpolasi.

Pertahankan operasi kisi berbasis nol biasa

Grid berbasis nol seharusnya tidak perlu modul pengganti Array2D hanya untuk menghindari jalur alokasi untuk kode dinamis. Implementasi Core yang diperbarui mendukung inisialisasi dan transformasi yang familier:

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, dan rebase gunakan jalur alokasi berbasis nol pada semua build Core. Bounds-preserving map, mapi, dan copy dapat menggunakan API alokasi .NET yang lebih baru dalam net10.0 build; build .NET Standard mempertahankan fallback mereka. Array berbasis non-nol tidak usang, tetapi pembatasan alokasi aslinya tetap ada.

Lihat Array dua dimensi dan AOT Asli untuk jalur alokasi dan batasan yang tersisa. Periksa diagnostik penerbitan seluruh aplikasi.

Terima kasih kepada @T-Gro atas peningkatan Array2D.

Lebih sedikit memori yang digunakan dan lebih sedikit pekerjaan yang berulang

F# 11 mengurangi analisis kompilator berulang dan beberapa alokasi memori pada aplikasi. Ini memengaruhi beban kerja yang berbeda.

Berbagi pustaka referensi antarproyek

Untuk menghindari salinan berulang dependensi biner bersama, layanan bahasa dapat berbagi grafik pustaka yang diimpor yang memenuhi syarat di seluruh proyek.

Dalam eksperimen retained memory milik PR implementasi, solusi ReSharper.FSharp sepuluh proyek turun dari 1.547,2 MB menjadi 1.002,2 MB dengan berbagi diaktifkan. Itu adalah pengukuran aktif/nonaktif untuk beban kerja tersebut, bukan pengurangan umum F# 10-ke-11. Solusi yang didominasi oleh referensi proyek daripada referensi biner bersama dapat mengalami peningkatan yang jauh lebih kecil.

Pembacaan metadata dan spesialisasi sebaris juga mengurangi alokasi sementara, yang merupakan metrik terpisah. Lihat Kinerja perkakas untuk cakupan dan kontrol.

Terima kasih kepada @auduchinok atas impor pustaka bersama.

Kompilasi tugas independen secara paralel secara bawaan

Kompilasi paralel sekarang diaktifkan secara default, dengan output deterministik dan aturan urutan sumber dipertahankan. Cache overload dan peningkatan pola berpagar mencegah pekerjaan berulang lainnya. Visual Studio/FCS juga mengurangi analisis terhadap dokumen yang tidak berubah dan membatasi pemeriksaan serentak; lihat Performa alat.

Hindari overhead alokasi callback dan hasil kosong

Fungsi List dan Array yang dipilih kini menyisipkan callback secara inline. Untuk helper inline Anda sendiri, OptimizeClosureIfNotInlined menyesuaikan callback curried opak satu kali:

let inline foldItems
    ([<InlineIfLambda; OptimizeClosureIfNotInlined>] folder)
    state
    (items: 'T[]) =
    let mutable total = state
    for item in items do
        total <- folder total item
    total

Alokasi lain dapat tetap ada. Lihat Mengoptimalkan callback yang di-curry untuk persyaratan.

Hasil kosong adalah kasus umum lain dari proses yang berhasil. Beban kerja FSharpLint yang memotivasi berulang kali menggabungkan aturan yang tidak menemukan tidak ada peringatan. Core sekarang menggunakan kembali array kosong bersama dalam lebih banyak operasi:

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

Perubahan menghapus objek larik kosong yang berlebihan, bukan setiap alokasi dalam alur. Jangan mengandalkan hasil kosong terpisah yang memiliki identitas referensi yang unik.

Berkat @T-Grocallback koleksi inline dan array kosong bersama.

Pemanggilan API yang lebih tepat dan kenyamanan sehari-hari

Harus menggunakan nama saat urutan parameter yang dihasilkan dapat berubah

Untuk melindungi API yang dihasilkan dari perubahan urutan parameter senyap, penulis dapat memerlukan argumen bernama:

Note

Enforcement adalah fitur F# 11.2; di SDK .NET 11.0.100, aktifkan pratinjau. Definisi yang tersedia System.Diagnostics.CodeAnalysis.RequireNamedArgumentsAttribute juga diperlukan. Contoh lengkap memberikan definisi kompatibilitas untuk referensi yang tidak memiliki definisi tersebut.

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.

Menyusun ulang deklarasi mempertahankan hasil panggilan bernama, 0.75M; panggilan posisional ditolak. Untuk kasus penggunaan API yang dihasilkan, penyedia harus memancarkan anotasi.

Mengenali argumen generik yang sudah dibungkus

Untuk menghindari pembungkusan tugas yang ada sebagai nilai hasil biasa, aturan overload pilihan terakhir yang baru mengutamakan Task<'T> daripada 'T tanpa pembungkus:

Note

Perilaku pemecah tiebreaker dan prioritas overload yang paling konkret adalah fitur F# 11.2; di .NET 11.0.100 SDK, aktifkan pratinjau.

open System.Threading.Tasks

let pending = Task.FromResult 42
let wrapped = ValueTask<_>(pending)
let answer: int = wrapped.Result
// ValueTask<int>, not ValueTask<Task<int>>

Hasilnya adalah ValueTask<int>, bukan ValueTask<Task<int>>. Lihat Pemilihan kelebihan beban untuk melihat batasan aturan.

Lebih memilih overload baru tanpa menghapus entry point lama

Library mungkin menyimpan metode diagnostik lama untuk biner yang ada sambil menambahkan overload yang menyertakan lokasi sumber pemanggil. OverloadResolutionPriorityAttribute membuat preferensi tersebut eksplisit untuk pemanggil yang baru dikompilasi:

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.

Panggilan yang tidak berubah memilih overload yang peka terhadap baris. Lihat Pemilihan overload untuk cakupan dan aturan kompatibilitas.

Gunakan jaminan nullability bersyarat .NET

Jika jalur file Anda non-null, hasil Path API yang dapat bernilai null tidak boleh memaksa pemeriksaan yang dikesampingkan oleh kontraknya sendiri. F# 11 menggunakan janji .NET NotNullIfNotNull yang ada: input bernama non-null menyiratkan hasil non-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

Aktifkan <Nullable>enable</Nullable>. Input nullable masih memerlukan penanganan nullable; lihat Kontrak pengembalian kondisional. Untuk API C# T? yang tidak dibatasi, gunakan Unchecked.withNull helper terpisah.

Terima kasih kepada @kerams untuk dukungan nullability bersyarat.

Perbaikan bahasa minor

Untuk memanggil tipe record dengan sintaks konstruktor .NET, gunakan F# 11.2 konstruktor record:

type Person = { Name: string; Age: int }
let person = Person(Name = "Ada", Age = 36)

Argumen bernama menghindari ketergantungan pada urutan bidang. Terima kasih kepada @charlesroddie atas fitur ini.

Untuk sumber kondisional, #elif menghindari grup #if bertingkat:

#if EU_WAREHOUSE
let dispatchTimeZone = "Europe/Paris"
#elif US_WAREHOUSE
let dispatchTimeZone = "America/New_York"
#else
let dispatchTimeZone = "UTC"
#endif

System.Console.WriteLine(dispatchTimeZone)

Cabang pertama yang cocok menang. Proyek sampel mendefinisikan EU_WAREHOUSE dan mencetak Europe/Paris; tanpa kedua simbol didefinisikan, outputnya adalah UTC.

Untuk kasus alat dan interop terkait:

Sebelum pembaruan

Ingatlah perubahan konsekuensial ini saat meningkatkan:

Kode yang terpengaruh Action
Pustaka dengan berkas .fsi Salin atribut kontrak yang diwajibkan ke dalam signature; ketiadaannya kini memicu error FS3888.
Library inline yang didukung oleh ekstensi Tinjau tanda tangan yang disimpulkan dan persyaratan produsen/konsumen. Inferensi F# 11.2 dapat mempertahankan batasan yang lebih umum.
Delegasi metode instans Validasikan receiver sebelum konstruksi langsung, ketika kegagalan receiver null sekarang terjadi.
Output interpolasi yang dilokalkan Placeholder format .NET menggunakan kultur invarian. Pilih kultur eksplisit jika output terlokalkan diperlukan.
Builder kustom dengan final do! Menyediakan pengikatan dan penyelesaian unit; final do! tidak lagi memilih anggota final-forwarding.
Belum selesai let! Tambahkan badan komputasi berikut; pengikatan yang tidak lengkap sekarang memiliki pesan diagnostik yang lebih langsung.
Pengaturan bahasa lama Mode bernomor yang didukung dimulai pada F# 8; mode indentasi non-ketat dihapus. Lihat Versi bahasa dan indentasi.
Operasi bitwise pada enum Gunakan enum dengan tipe dasar integral; tipe dasar yang tidak valid akan ditolak.