Mengumumkan Pratinjau F # 4.6

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 blog

Mulai


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 |} // Two anonymous record types have mismatched sets of field names '["Area"; "Circumference"; "Diameter"]' and '["Area"; "Diameter"; "MyCircumference"]' 

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 // Note that the keyword comes before the '{| |}' brace pair struct {| Area=a; Circumference=c; Diameter=d |} // the 'struct' keyword also comes before the '{| |}' brace pair when declaring the parameter type 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 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 

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 // This will fail to compile for hopefully obvious reasons! printCircleStats r struct {| Area=4.0; Circumference=12.6; Diameter=12.6 |} 

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 |} // Gives {| X=1; Y=2; Z=3 |} let stringifiedY = {| expandedData with Y="Hello!" |} // Gives {| X=1; Y="Hello!"; Z=3 |} 

Ekspresi asli juga bisa menjadi tipe rekaman:


 type R = { X: int } let data = { X=1 } let data' = {| data with Y = 2 |} // Gives {| X=1; Y=2 |} 

Anda juga dapat menyalin data ke dan dari referensi dan menyusun catatan anonim:


 // Copy data from a reference record into a struct anonymous record type R1 = { X: int } let r1 = { X=1 } let data1 = struct {| r1 with Y=1 |} // Copy data from a struct record into a reference anonymous record [<Struct>] type R2 = { X: int } let r2 = { X=1 } let data2 = {| r1 with Y=1 |} 

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 |} // true {| a = 1+1 |} > {| a = 1 |} // true 

Namun, jenis yang dibandingkan harus memiliki "bentuk" yang sama:


 // error FS0001: Two anonymous record types have mismatched sets of field names '["a"]' and '["a"; "b"]' {| a = 1+1 |} = {| a = 2; b = 1|} 

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 [] // None List.tryExactlyOne [1] // Some 1 List.tryExactlyOne [1; 2] // None Array.tryExactlyOne null // ArgumentNullException Array.tryExactlyOne [||] // None Array.tryExactlyOne [|1|] // Some 1 Array.tryExactlyOne [|1; 2|] // None Seq.tryExactlyOne null // ArgumentNullException Seq.tryExactlyOne (Seq.ofList []) // None Seq.tryExactlyOne (Seq.ofList [1]) // Some 1 Seq.tryExactlyOne (Seq.ofList [1; 2]) // None 

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!

Source: https://habr.com/ru/post/id437666/


All Articles