
Kami sangat senang mengumumkan bahwa Visual Studio 2019 akan mengirimkan versi baru F # ketika rilis: F # 4.6!
F # 4.6 adalah pemutakhiran yang lebih kecil untuk bahasa F #, menjadikannya sebuah rilis point βbenarβ. Seperti versi F # sebelumnya, F # 4.6 dikembangkan sepenuhnya melalui proses RFC terbuka (permintaan komentar). Komunitas F # telah menawarkan umpan balik yang sangat rinci dalam diskusi untuk versi bahasa ini. Anda dapat melihat semua RFC yang sesuai dengan rilis ini di sini:
Posting ini akan merinci set fitur dan cara memulai.
Asli di blogMulai
Pertama, instal:
Selanjutnya, perbarui ketergantungan FSharp.Core Anda ke FSharp.Core 4.6 (atau lebih tinggi). Jika Anda menggunakan Visual Studio, Anda dapat melakukan ini dengan UI Manajemen Paket NuGet. Jika Anda tidak menggunakan Visual Studio, atau lebih suka file proyek pengeditan tangan, tambahkan ini ke file proyek:
<ItemGroup> <PackageReference Update="FSharp.Core" Version="4.6.0" /> </ItemGroup>
Setelah Anda menginstal bit yang diperlukan, Anda dapat menggunakan F # 4.6 dengan Visual Studio , Visual Studio untuk Mac , atau Visual Studio Code dengan Ionide .
Catatan anonim
Selain dari berbagai perbaikan bug, satu-satunya perubahan bahasa di F # 4.6 adalah pengenalan tipe Catatan Anonim .
Penggunaan dasar
Dari perspektif F # -hanya, Catatan Anonim adalah tipe catatan F # yang tidak memiliki nama yang dapat dijelaskan dan dapat dideklarasikan dalam fasi ad-hoc. Meskipun mereka tidak mungkin secara mendasar mengubah cara Anda menulis kode F #, mereka mengisi banyak celah yang lebih kecil yang telah ditemui programmer F # dari waktu ke waktu, dan dapat digunakan untuk manipulasi data ringkas yang sebelumnya tidak mungkin.
Mereka cukup mudah digunakan. Misalnya, di sini cara Anda dapat berinteraksi dengan fungsi yang menghasilkan catatan anonim:
open System let circleStats radius = let d = radius * 2.0 let a = Math.PI * (radius ** 2.0) let c = 2.0 * Math.PI * radius {| Diameter=d; Area=a; Circumference=c |} let r = 2.0 let stats = circleStats r printfn "Circle with radius: %f has diameter %f, area %f, and circumference %f" r stats.Diameter stats.Area stats.Circumference
Namun, mereka dapat digunakan untuk lebih dari sekadar wadah data dasar. Berikut ini memperluas sampel sebelumnya untuk menggunakan fungsi pencetakan yang lebih aman:
let circleStats radius = let d = radius * 2.0 let a = Math.PI * (radius ** 2.0) let c = 2.0 * Math.PI * radius {| Diameter=d; Area=a; Circumference=c |} let printCircleStats r (stats: {| Area: float; Circumference: float; Diameter: float |}) = printfn "Circle with radius: %f has diameter %f, area %f, and circumference %f" r stats.Diameter stats.Area stats.Circumference let r = 2.0 let stats = circleStats r printCircleStats r stats
Jika Anda mencoba memanggil `printCircleStats` dengan catatan anonim yang memiliki tipe data dasar yang sama tetapi label berbeda, itu akan gagal dikompilasi:
printCircleStats r {| Diameter=2.0; Area=4.0; MyCircumference=12.566371 |}
Ini persis bagaimana tipe catatan F # bekerja, kecuali semuanya telah dinyatakan ad-hoc daripada di muka. Ini memiliki manfaat dan kelemahan tergantung pada situasi khusus Anda, jadi kami sarankan untuk menggunakan catatan anonim secara bijaksana daripada mengganti semua deklarasi F # catatan depan Anda.
Buat catatan anonim
Catatan anonim juga bisa menjadi struct dengan menggunakan kata kunci struct :
open System let circleStats radius = let d = radius * 2.0 let a = Math.PI * (radius ** 2.0) let c = 2.0 * Math.PI * radius
Anda dapat memanggil fungsi yang mengambil catatan anonim terstruktur dapat dilakukan secara eksplisit seperti ini:
let printCircleStats r (stats: struct {| Area: float; Circumference: float; Diameter: float |}) = printfn "Circle with radius: %f has diameter %f, area %f, and circumference %f" r stats.Diameter stats.Area stats.Circumference printCircleStats r struct {| Area=4.0; Circumference=12.6; Diameter=12.6 |}
Atau Anda dapat menggunakan "intruktur inferensi" untuk menghilangkan `struct` di situs panggilan:
let printCircleStats r (stats: struct {| Area: float; Circumference: float; Diameter: float |}) = printfn "Circle with radius: %f has diameter %f, area %f, and circumference %f" r stats.Diameter stats.Area stats.Circumference printCircleStats r {| Area=4.0; Circumference=12.6; Diameter=12.6 |}
Ini akan memperlakukan instance dari catatan anonim yang Anda buat seolah-olah itu adalah struct.
Perhatikan bahwa kebalikannya tidak benar:
let printCircleStats r (stats: {| Area: float; Circumference: float; Diameter: float |}) = printfn "Circle with radius: %f has diameter %f, area %f, and circumference %f" r stats.Diameter stats.Area stats.Circumference
Saat ini tidak memungkinkan untuk mendefinisikan IsByRefLike atau IsReadOnly struct tipe catatan anonim. Ada saran bahasa yang mengusulkan peningkatan ini, tetapi karena keanehan dalam sintaksis masih dalam diskusi.
Mengambil lebih jauh
Catatan anonim dapat digunakan dalam rangkaian konteks yang lebih luas.
Catatan anonim bisa serial
Anda dapat membuat cerita bersambung dan deserialisasi catatan anonim:
open Newtonsoft.Json let phillip = {| name="Phillip"; age=28 |} let str = JsonConvert.SerializeObject(phillip) printfn "%s" str let phillip' = JsonConvert.DeserializeObject<{|name: string; age: int|}>(str) printfn "Name: %s Age: %d" phillip'.name phillip'.age
Ini menghasilkan apa yang Anda harapkan:
{"age":28,"name":"Phillip"} Name: Phillip Age: 28
Berikut ini contoh perpustakaan yang juga dipanggil dalam proyek lain:
namespace AnonyRecdOne open Newtonsoft.Json module AR = let serialize () = let phillip = {| name="Phillip"; age=28 |} JsonConvert.SerializeObject(phillip)
open AnonyRecdOne open Newtonsoft.Json [<EntryPoint>] let main _ = let str = AR.serialize () let phillip = JsonConvert.DeserializeObject<{|name: string; age: int|}>(str) printfn "Name: %s Age: %d" phillip.name phillip.age
Ini mungkin membuat segalanya lebih mudah untuk skenario seperti data ringan melewati jaringan dalam sistem yang terdiri dari layanan-layanan microser.
Catatan anonim dapat digabungkan dengan definisi tipe lainnya
Anda mungkin memiliki model data mirip pohon di domain Anda, seperti contoh berikut:
type FullName = { FirstName: string; LastName: string } type Employee = | Engineer of FullName | Manager of name: FullName * reports: Employee list | Executive of name: FullName * reports: Employee list * assistant: Employee
Merupakan hal yang umum untuk melihat kasus yang dimodelkan sebagai tupel dengan bidang serikat bernama, tetapi karena data semakin rumit, Anda dapat mengekstrak setiap kasus dengan catatan:
type FullName = { FirstName: string; LastName: string } type Employee = | Engineer of FullName | Manager of Manager | Executive of Executive and Manager = { Name: FullName; Reports: Employee list } and Executive = { Name: FullName; Reports: Employee list; Assistant: Employee }
Definisi rekursif ini sekarang dapat disingkat dengan catatan anonim jika sesuai dengan basis kode Anda:
type FullName = { FirstName: string; LastName: string } type Employee = | Engineer of FullName | Manager of {| Name: FullName; Reports: Employee list |} | Executive of {| Name: FullName; Reports: Employee list; Assistant: Employee |}
Seperti contoh-contoh sebelumnya, teknik ini harus diterapkan secara bijaksana dan bila dapat diterapkan pada skenario Anda.
Catatan anonim memudahkan penggunaan LINQ di F #
Programmer F # biasanya lebih suka menggunakan kombinator Daftar, Array, dan Urutan saat bekerja dengan data, tetapi kadang-kadang bisa membantu menggunakan LINQ . Ini secara tradisional agak menyakitkan, karena LINQ menggunakan tipe C # anonim.
Dengan catatan anonim, Anda bisa menggunakan metode LINQ seperti yang Anda lakukan dengan C # dan jenis anonim:
open System.Linq let names = [ "Ana"; "Felipe"; "Emillia"] let nameGrouping = names.Select(fun n -> {| Name=n; FirstLetter=n.[0] |}) for ng in nameGrouping do printfn "%s has first letter %c" ng.Name ng.FirstLetter
Ini mencetak:
Ana has first letter A Felipe has first letter F Emillia has first letter E
Catatan anonim memudahkan bekerja dengan Entity Framework dan ORM lainnya
Pemrogram F # yang menggunakan ekspresi permintaan F # untuk berinteraksi dengan database harus melihat beberapa peningkatan kualitas hidup yang kecil dengan catatan anonim.
Sebagai contoh, Anda mungkin terbiasa menggunakan tuple untuk mengelompokkan data dengan klausa `select`:
let q = query { for row in db.Status do select (row.StatusID, row.Name) }
Tetapi ini menghasilkan kolom dengan nama seperti Item1 dan Item2 yang tidak ideal. Sebelum catatan anonim, Anda harus mendeklarasikan jenis catatan dan menggunakannya. Sekarang Anda tidak perlu melakukan itu:
let q = query { for row in db.Status do select {| StatusID = row.StatusID; Name = row.Name |} }
Tidak perlu menentukan tipe catatan di depan! Ini membuat ekspresi kueri jauh lebih selaras dengan SQL aktual yang mereka modelkan.
Catatan anonim juga memungkinkan Anda menghindari membuat jenis AnonymousObject dalam kueri yang lebih canggih hanya untuk membuat pengelompokan data ad-hoc untuk keperluan kueri.
Catatan anonim memudahkan penggunaan perutean kustom di ASP.NET Core
Anda mungkin menggunakan ASP.NET Core dengan F #, tetapi mungkin mengalami kesulitan ketika mendefinisikan rute kustom. Seperti contoh sebelumnya, ini masih bisa dilakukan dengan mendefinisikan tipe catatan di muka, tetapi ini sering dianggap tidak perlu oleh pengembang F #. Sekarang Anda dapat melakukannya sebaris:
app.UseMvc(fun routes -> routes.MapRoute("blog","blog/{*article}", defaults={| controller="Blog"; action="Article" |}) |> ignore ) |> ignore
Ini masih tidak ideal karena fakta bahwa F # ketat tentang jenis pengembalian (tidak seperti C #, di mana Anda tidak perlu secara eksplisit mengabaikan hal-hal yang mengembalikan nilai). Namun, ini memungkinkan Anda menghapus definisi rekaman yang telah ditetapkan sebelumnya yang tidak melayani tujuan lain selain untuk memungkinkan Anda mengirim data ke dalam pipa middleware ASP.NET.
Salin dan perbarui ekspresi dengan catatan anonim
Seperti jenis Rekaman, Anda dapat menggunakan sintaks salin dan perbarui dengan catatan anonim:
let data = {| X = 1; Y = 2 |} let expandedData = {| data with Z = 3 |}
Ekspresi asli juga bisa menjadi tipe rekaman:
type R = { X: int } let data = { X=1 } let data' = {| data with Y = 2 |}
Anda juga dapat menyalin data ke dan dari referensi dan menyusun catatan anonim:
Penggunaan ekspresi salin dan perbarui memberi catatan anonim tingkat fleksibilitas tinggi ketika bekerja dengan data dalam F #.
Kesetaraan dan pencocokan pola
Catatan anonim secara struktural setara dan sebanding:
{| a = 1+1 |} = {| a = 2 |}
Namun, jenis yang dibandingkan harus memiliki "bentuk" yang sama:
Meskipun Anda dapat menyamakan dan membandingkan catatan anonim, Anda tidak dapat mencocokkan pola di atasnya. Ini karena dua alasan:
- Pola harus memperhitungkan setiap bidang catatan anonim, tidak seperti jenis rekaman. Ini karena catatan anonim tidak mendukung subtyping struktural - mereka adalah tipe nominal.
- Tidak ada kemampuan untuk memiliki pola tambahan dalam ekspresi kecocokan pola, karena setiap pola yang berbeda akan menyiratkan jenis rekaman anonim yang berbeda.
- Persyaratan untuk memperhitungkan setiap bidang dalam catatan anonim akan membuat pola lebih verbose daripada penggunaan notasi "dot".
Sebagai gantinya, "dot" -syntax digunakan untuk mengekstraksi nilai dari catatan anonim. Ini akan selalu paling banyak sebagai verbose seolah-olah pencocokan pola digunakan, dan dalam praktiknya cenderung kurang verbose karena tidak selalu mengekstraksi setiap nilai dari catatan anonim. Berikut ini cara bekerja dengan contoh sebelumnya di mana catatan anonim adalah bagian dari serikat yang didiskriminasi:
type Employee = | Engineer of FullName | Manager of {| Name: FullName; Reports: Employee list |} | Executive of {| Name: FullName; Reports: Employee list; Assistant: Employee |} let getFirstName e = match e with | Engineer fullName -> fullName.FirstName | Manager m -> m.Name.FirstName | Executive ex -> ex.Name.FirstName
Saat ini ada saran terbuka untuk memungkinkan pencocokan pola pada catatan anonim dalam konteks terbatas yang sebenarnya dapat diaktifkan. Jika Anda memiliki usulan use case, silakan gunakan masalah itu untuk membahasnya!
Penambahan FSharp.Core
Itu tidak akan menjadi rilis F # lain tanpa tambahan pada F # Core Library!
Perluasan ValueOption
Tipe ValueOption yang diperkenalkan di F # 4.5 sekarang memiliki beberapa barang tambahan yang terlampir pada tipe:
- Atribut DebuggerDisplay untuk membantu debugging
- IsNone , IsSome , None , Some , op_Implicit , dan anggota ToString
Ini memberinya "paritas" dengan jenis Opsi.
Selain itu, sekarang ada modul ValueOption yang berisi fungsi yang sama dengan modul `Option`:
module ValueOption = [<CompiledName("IsSome")>] val inline isSome: voption:'T voption -> bool [<CompiledName("IsNone")>] val inline isNone: voption:'T voption -> bool [<CompiledName("DefaultValue")>] val defaultValue: value:'T -> voption:'T voption -> 'T [<CompiledName("DefaultWith")>] val defaultWith: defThunk:(unit -> 'T) -> voption:'T voption -> 'T [<CompiledName("OrElse")>] val orElse: ifNone:'T voption -> voption:'T voption -> 'T voption [<CompiledName("OrElseWith")>] val orElseWith: ifNoneThunk:(unit -> 'T voption) -> voption:'T voption -> 'T voption [<CompiledName("GetValue")>] val get: voption:'T voption -> 'T [<CompiledName("Count")>] val count: voption:'T voption -> int [<CompiledName("Fold")>] val fold<'T,'State> : folder:('State -> 'T -> 'State) -> state:'State -> voption:'T voption -> 'State [<CompiledName("FoldBack")>] val foldBack<'T,'State> : folder:('T -> 'State -> 'State) -> voption:'T voption -> state:'State -> 'State [<CompiledName("Exists")>] val exists: predicate:('T -> bool) -> voption:'T voption -> bool [<CompiledName("ForAll")>] val forall: predicate:('T -> bool) -> voption:'T voption -> bool [<CompiledName("Contains")>] val inline contains: value:'T -> voption:'T voption -> bool when 'T : equality [<CompiledName("Iterate")>] val iter: action:('T -> unit) -> voption:'T voption -> unit [<CompiledName("Map")>] val map: mapping:('T -> 'U) -> voption:'T voption -> 'U voption [<CompiledName("Map2")>] val map2: mapping:('T1 -> 'T2 -> 'U) -> voption1: 'T1 voption -> voption2: 'T2 voption -> 'U voption [<CompiledName("Map3")>] val map3: mapping:('T1 -> 'T2 -> 'T3 -> 'U) -> 'T1 voption -> 'T2 voption -> 'T3 voption -> 'U voption [<CompiledName("Bind")>] val bind: binder:('T -> 'U voption) -> voption:'T voption -> 'U voption [<CompiledName("Flatten")>] val flatten: voption:'T voption voption -> 'T voption [<CompiledName("Filter")>] val filter: predicate:('T -> bool) -> voption:'T voption -> 'T voption [<CompiledName("ToArray")>] val toArray: voption:'T voption -> 'T[] [<CompiledName("ToList")>] val toList: voption:'T voption -> 'T list [<CompiledName("ToNullable")>] val toNullable: voption:'T voption -> Nullable<'T> [<CompiledName("OfNullable")>] val ofNullable: value:Nullable<'T> -> 'T voption [<CompiledName("OfObj")>] val ofObj: value: 'T -> 'T voption when 'T : null [<CompiledName("ToObj")>] val toObj: value: 'T voption -> 'T when 'T : null
Ini harus meredakan kekhawatiran bahwa `ValueOption` adalah saudara aneh dari` Option` yang tidak mendapatkan serangkaian fungsi yang sama.
tryExactlyOne untuk Daftar, Array, dan Seq
Fungsi bagus ini dikontribusikan oleh Grzegorz Dziadkiewicz . Begini cara kerjanya:
List.tryExactlyOne []
Membungkus
Meskipun daftar total fitur di F # 4.6 tidak besar, mereka masih masuk cukup dalam! Kami mendorong Anda untuk mencoba F # 4.6 dan meninggalkan kami umpan balik sehingga kami dapat menyempurnakan hal-hal sebelum rilis penuh. Seperti biasa, terima kasih kepada komunitas F # atas kontribusi mereka - baik dalam diskusi kode dan desain - yang membantu kami terus memajukan bahasa F #.
Cheers, dan selamat retas!