Bagikan melalui


Apa yang baru di F# 10

F# 10 memberi Anda beberapa peningkatan pada bahasa F#, pustaka FSharp.Core, dan peralatan. Versi ini adalah rilis penyempurnaan yang berfokus pada kejelasan, konsistensi, dan performa, dengan peningkatan kecil tetapi bermakna yang membuat kode sehari-hari Anda lebih mudah terbaca dan kuat. F# 10 dikirim dengan .NET 10 dan Visual Studio 2026.

Anda dapat mengunduh .NET SDK terbaru dari halaman unduhan .NET.

Get started

F# 10 tersedia di semua distribusi .NET Core dan alat Visual Studio. Untuk informasi selengkapnya, lihat Mulai menggunakan F#.

Penekanan peringatan terlingkup

Anda sekarang dapat menekan peringatan di bagian tertentu dari kode Anda menggunakan arahan baru #warnon . Ini dipasangkan dengan arahan #nowarn yang ada untuk memberi Anda kontrol yang tepat tentang di mana peringatan diterapkan.

Sebelumnya, ketika Anda menggunakan #nowarn, hal itu akan menonaktifkan peringatan untuk sisa file dan dapat mengabaikan masalah yang sah di bagian lain. Mari kita lihat contoh yang memotivasi:

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

Jika Anda menambahkan #nowarn 25 di atas definisi fungsi, itu menonaktifkan FS0025 untuk seluruh sisa file.

Dengan F# 10, Anda sekarang dapat menandai bagian yang tepat di mana Anda ingin peringatan ditekan:

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

Sebaliknya, jika peringatan dinonaktifkan secara global (misalnya, melalui bendera kompilator), Anda dapat mengaktifkannya secara lokal dengan #warnon. Arahan ini akan berlaku hingga pencocokan #nowarn atau hingga akhir dari file.

Catatan kompatibilitas penting:

Fitur ini mencakup beberapa perubahan yang meningkatkan konsistensi arahan #nowarn/#warnon . Ini adalah perubahan yang melanggar:

  • Pengkompilasi tidak lagi mengizinkan direktif peringatan multibaris dan kosong.
  • Pengkompilasi tidak lagi mengizinkan spasi kosong antara # dan nowarn.
  • Anda tidak dapat menggunakan string dengan tiga tanda kutip, terinterpolasi, atau verbatim untuk nomor peringatan.

Perilaku skrip juga telah berubah. Sebelumnya, ketika Anda menambahkan #nowarn direktif di mana saja dalam skrip, itu diterapkan ke seluruh kompilasi. Sekarang, perilakunya dalam skrip cocok dengan yang ada dalam .fs file, hanya berlaku hingga akhir file atau yang sesuai #warnon.

Fitur ini mengimplementasikan RFC FS-1146.

Pengubah akses pada pengaktif properti otomatis

Pola umum dalam pemrograman berorientasi objek adalah membuat status yang dapat dibaca secara publik tetapi dapat diubah secara privat. Sebelum F# 10, Anda memerlukan sintaks properti eksplisit dengan bidang pencadangan (variabel tersembunyi yang menyimpan nilai properti aktual) untuk mencapai ini, yang menambahkan kode berulang:

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

Dengan F# 10, Anda sekarang dapat menerapkan pengubah akses yang berbeda ke masing-masing aksesor properti. Ini memungkinkan Anda menentukan tingkat akses yang berbeda untuk getter dan setter suatu properti, sehingga pola menjadi lebih sederhana:

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

Anda dapat menempatkan pengubah akses baik sebelum nama properti (berlaku untuk kedua aksesor) atau sebelum aksesor individual, tetapi tidak keduanya secara bersamaan.

Perhatikan bahwa fitur ini tidak diperluas ke file tanda tangan (.fsi). Tanda tangan yang benar untuk contoh di Ledger atas adalah:

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

Fitur ini mengimplementasikan RFC FS-1141.

Parameter opsional ValueOption

Anda sekarang dapat menggunakan representasi berbasis ValueOption<'T> struct untuk parameter opsional. Saat Anda menerapkan atribut [<Struct>] ke parameter opsional, compiler menggunakan ValueOption<'T> alih-alih tipe berbasis referensi option. Ini menghindari alokasi timbunan (memori yang dialokasikan pada tumpukan terkelola yang memerlukan pengumpulan sampah) untuk pembungkus opsi, yang bermanfaat dalam kode kritis performa.

Sebelumnya, F# selalu menggunakan jenis yang dialokasikan di heap option untuk parameter opsional, bahkan ketika parameter tidak ada.

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

Di F# 10, Anda dapat menggunakan atribut [<Struct>] untuk memanfaatkan ValueOption yang didukung oleh struct.

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

Ini menghilangkan alokasi timbunan ketika argumen tidak ada, yang bermanfaat dalam kode kritis performa.

Pilih opsi berbasis struktur ini untuk nilai kecil atau jenis yang sering dibangun di mana tekanan alokasi penting. Gunakan referensi default berbasis option saat Anda mengandalkan pembantu pencocokan pola yang sudah ada, memerlukan semantik referensi, atau ketika perbedaan performa tidak signifikan. Fitur ini memperkuat paritas dengan konstruksi bahasa F# lainnya yang sudah mendukung ValueOption.

Dukungan pemanggilan ekor dalam ekspresi komputasi

F# 10 menambahkan pengoptimalan panggilan ekor untuk ekspresi komputasi. Penyusun ekspresi komputasi sekarang dapat memilih pengoptimalan ini dengan menerapkan metode khusus.

Ketika kompilator menerjemahkan ekspresi komputasi ke dalam kode F# reguler (proses yang disebut desugaring), ia mengenali kapan ekspresi seperti return!, yield!, atau do! muncul dalam posisi ekor. Jika penyusun Anda menyediakan metode berikut, pengkompilasi merutekan panggilan tersebut ke titik masuk yang dioptimalkan:

  • ReturnFromFinal - memanggil ekor return! (menggunakan ReturnFrom jika tidak ada)
  • YieldFromFinal - dipanggil untuk bagian akhir yield! (beralih ke YieldFrom jika tidak ada)
  • Untuk terminal do!, pengkompilasi lebih suka ReturnFromFinal, lalu YieldFromFinal, sebelum kembali ke jalur normal Bind

Anggota ini *Final bersifat opsional dan ada murni untuk mengaktifkan pengoptimalan. Pembangun yang tidak menyediakan anggota ini menjaga semantik yang ada tetap tidak berubah.

Contohnya:

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

Namun, bukan pada posisi akhir:

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

Catatan kompatibilitas penting:

Perubahan ini dapat menyebabkan kerusakan jika penyusun ekspresi komputasi sudah menentukan anggota dengan nama-nama ini. Dalam kebanyakan kasus, penyusun yang sudah ada tetap berfungsi tanpa modifikasi saat dikompilasi dengan F# 10. Pengkompilasi yang lebih lama akan mengabaikan metode baru *Final , sehingga penyusun yang harus tetap kompatibel dengan versi kompilator sebelumnya tidak boleh mengasumsikan pengkompilasi akan memanggil metode ini.

Fitur ini mengimplementasikan RFC FS-1330.

Pengikatan yang ditik dalam ekspresi komputasi tanpa tanda kurung

F# 10 menghapus persyaratan untuk tanda kurung saat menambahkan anotasi tipe ke pengikatan ekspresi komputasi. Anda sekarang dapat menambahkan anotasi jenis pada let!, use!, dan and! menggunakan sintaks yang sama dengan let pengikatan biasa.

Sebelumnya, Anda harus menggunakan tanda kurung untuk anotasi jenis:

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

Di F# 10, Anda dapat menulis anotasi jenis tanpa tanda kurung:

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

Perbolehkan _ dalam use! pengikatan

Anda sekarang dapat menggunakan pola pembuangan (_) dalam pengikatan di dalam ekspresi komputasi use!. Ini menyelaraskan perilaku use! dengan pengikatan reguler use .

Sebelumnya, kompilator menolak pola buang dalam use! pengikatan, memaksa Anda untuk membuat pengidentifikasi sementara.

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

Di F# 10, Anda dapat menggunakan pola buang secara langsung:

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

Ini mengklarifikasi niat saat mengikat sumber daya asinkron yang nilainya hanya diperlukan untuk manajemen seumur hidup.

Menolak modul berlapis pseudo dalam jenis

Pengkompilasi sekarang menghasilkan kesalahan ketika Anda menempatkan deklarasi module yang diindentasi pada tingkat struktural yang sama di dalam definisi tipe. Ini memperketat validasi struktural untuk menolak penempatan modul yang menyesatkan dalam jenis.

Sebelumnya, kompilator menerima deklarasi yang module diindentasi dalam definisi jenis, tetapi sebenarnya membuat modul ini sebagai saudara kandung untuk jenis daripada menyarangkannya di dalamnya:

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

Dengan F# 10, pola ini menimbulkan kesalahan FS0058, memaksa Anda untuk mengklarifikasi niat Anda dengan penempatan modul yang tepat:

type U =
    | A
    | B

module M =
    let f () = ()

Peringatan penghentian untuk dihilangkan seq

Kompiler sekarang memperingatkan Anda tentang ekspresi urutan telanjang yang menghilangkan penyusun seq . Saat Anda menggunakan kurung kurawal rentang kosong seperti { 1..10 }, Anda akan melihat peringatan penghentian yang mendorong Anda untuk menggunakan formulir eksplisit seq { ... } .

Secara historis, F# mengizinkan sintaks "sequence comprehension lite" dalam kasus khusus di mana Anda bisa tidak menyertakan kata kunci seq.

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

Dalam F# 10, kompilator memperingatkan tentang pola ini dan mendorong bentuk eksplisit:

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

Ini saat ini adalah peringatan, bukan kesalahan, memberi Anda waktu untuk memperbarui basis kode Anda. Jika Anda ingin menekan peringatan ini, gunakan NoWarn properti dalam file proyek atau #nowarn direktif Anda secara lokal dan berikan nomor peringatan: 3873.

Formulir eksplisit seq meningkatkan kejelasan kode dan konsistensi dengan ekspresi komputasi lainnya. Versi F# di masa mendatang mungkin membuat kesalahan ini, jadi kami sarankan mengadopsi sintaks eksplisit saat Anda memperbarui kode Anda.

Fitur ini mengimplementasikan RFC FS-1033.

Penegakan aturan target atribut

F# 10 memberlakukan validasi target atribut di semua konstruksi bahasa. Pengompilator sekarang memvalidasi bahwa atribut hanya diterapkan ke target yang dimaksudkan dengan memeriksa AttributeTargets pada nilai yang diikat dengan 'let', fungsi, kasus penyatuan, konstruktor implisit, struktur, dan kelas.

Sebelumnya, kompilator secara diam-diam memungkinkan Anda untuk salah menerapkan atribut ke target yang tidak kompatibel. Ini menyebabkan bug halus, seperti atribut pengujian diabaikan ketika Anda lupa () membuat fungsi:

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

Di F# 10, kompilator memberlakukan target atribut dan memunculkan peringatan saat atribut tidak diterapkan:

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

Catatan kompatibilitas penting:

Ini adalah perubahan signifikan yang mungkin mengungkapkan masalah yang sebelumnya tidak terlihat di basis kode Anda. Kesalahan awal mencegah masalah penemuan pengujian dan memastikan bahwa atribut seperti penganalisis dan dekorator berlaku seperti yang dimaksudkan.

Dukungan untuk and! pada ekspresi tugas

Anda sekarang dapat menunggu beberapa tugas secara bersamaan menggunakan and! dalam ekspresi tugas. Menggunakan task adalah cara populer untuk bekerja dengan alur kerja asinkron di F#, terutama ketika Anda membutuhkan interoperabilitas dengan C#. Namun, hingga saat ini, tidak ada cara ringkas untuk menunggu beberapa tugas secara bersamaan dalam ekspresi komputasi.

Mungkin Anda mulai dengan kode yang menunggu komputasi secara berurutan:

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

Jika Anda kemudian ingin mengubahnya untuk menunggu mereka secara bersamaan, Anda biasanya akan menggunakan 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
}

Di F# 10, Anda dapat menggunakan and! untuk pendekatan yang lebih idiomatik:

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

Ini menggabungkan semantik versi konkuren dengan kesederhanaan versi berurutan.

Fitur ini mengimplementasikan saran bahasa F# #1363, dan diterapkan sebagai penambahan pada FSharp.Core pustaka. Sebagian besar proyek mendapatkan versi FSharp.Core terbaru secara otomatis dari pengkompilasi, kecuali jika secara eksplisit menyematkan versi. Dalam hal ini, Anda harus memperbaruinya untuk menggunakan fitur ini.

Pemangkasan yang lebih baik secara default

F# 10 menghilangkan masalah gesekan yang sudah lama ada dengan pengurangan rakitan F#. Pemangkasan adalah proses menghapus kode yang tidak digunakan dari aplikasi yang diterbitkan untuk mengurangi ukurannya. Anda tidak lagi harus memelihara file ILLink.Substitutions.xml secara manual hanya untuk menghapus blok sumber daya metadata F# yang besar (data tanda tangan dan pengoptimalan yang digunakan oleh pengkompilator, tetapi tidak diperlukan oleh aplikasi Anda saat runtime).

Saat Anda menerbitkan dengan pemangkasan diaktifkan (PublishTrimmed=true), build F# sekarang secara otomatis menghasilkan file substitusi tersemat yang menargetkan sumber daya F# khusus alat ini.

Sebelumnya, Anda harus mempertahankan file ini secara manual untuk menghapus metadata. Ini menambah beban pemeliharaan dan mudah dilupakan.

Hasilnya adalah output yang lebih kecil secara default, kode yang kurang berulang untuk dipertahankan, dan satu bahaya pemeliharaan yang lebih sedikit. Jika Anda memerlukan kontrol manual penuh, Anda masih dapat menambahkan file substitusi Anda sendiri. Anda dapat menonaktifkan pembuatan otomatis dengan <DisableILLinkSubstitutions>false</DisableILLinkSubstitutions> properti .

Kompilasi paralel pada tahap pratinjau

Pembaruan menarik bagi pengguna F# yang ingin mengurangi waktu kompilasi: fitur kompilasi paralel menstabilkan. Dimulai dengan .NET 10, tiga fitur: pemeriksaan jenis berbasis grafik, pembuatan kode IL paralel, dan pengoptimalan paralel, dikelompokkan bersama di bawah ParallelCompilation properti proyek.

F# 10 memungkinkan pengaturan ini secara default untuk proyek menggunakan LangVersion=Preview. Kami berencana untuk mengaktifkannya untuk semua proyek di .NET 11.

Pastikan untuk mencobanya dan melihat apakah itu mempercepat kompilasi Anda. Untuk mengaktifkan kompilasi paralel di F# 10:

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

Jika Anda ingin menolak saat masih menikmati fitur pratinjau lainnya, atur ParallelCompilation ke false:

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

Kompilasi paralel dapat secara signifikan mengurangi waktu kompilasi untuk proyek dengan beberapa file dan dependensi.

Ketik cache subsumption

Pengkompilasi sekarang menyimpan pemeriksaan hubungan jenis untuk mempercepat inferensi jenis dan meningkatkan performa IDE, terutama ketika bekerja dengan hierarki jenis kompleks. Dengan menyimpan dan menggunakan kembali hasil dari pemeriksaan subsumption sebelumnya, pengkompilasi menghindari komputasi redundan yang sebelumnya memperlambat kompilasi dan IntelliSense.

Mengelola cache:

Dalam kebanyakan kasus, cache pensubsumsi tipe meningkatkan performa tanpa konfigurasi apa pun. Namun, jika Anda mengalami peningkatan jejak memori atau peningkatan penggunaan CPU (karena pekerja pemeliharaan cache), Anda dapat menyesuaikan perilaku cache:

  • Untuk menonaktifkan cache sepenuhnya, atur <LangVersion>9</LangVersion> dalam file proyek Anda untuk kembali ke perilaku F# 9.
  • Untuk menonaktifkan pengeluaran cache asinkron (yang meningkatkan tekanan utas) dan menggunakan pengeluaran sinkron sebagai gantinya FSharp_CacheEvictionImmediate=1 , atur variabel lingkungan.