F # 8: Serikat yang Didiskriminasi

Jadi perjalanan F # kami berlanjut. Kami melihat beberapa tipe dasar blok bangunan, seperti catatan / tupel, sekarang saatnya untuk melihat pada asosiasi yang ditandai.

Serikat pekerja berlabel memberikan dukungan untuk nilai, yang dapat menjadi salah satu dari beberapa nilai yang mungkin. Nilai yang mungkin dikenal sebagai "kasus gabungan", dan mengambil formulir yang ditunjukkan di bawah ini:

case-identifier1 [of [ fieldname1 : ] type1 [ * [ fieldname2 : ] type2 …] 

Jangan khawatir jika sintaks ini terlihat mengintimidasi, maka sebenarnya apa yang muncul adalah adanya label sehingga setiap case dapat dikenali (dibedakan) dari yang lain, dan tipe untuk case combining. Nama memiliki aturan tertentu, seperti

  • Harus dimulai dengan huruf kapital
  • Ini bisa menjadi pengidentifikasi, termasuk nama jenis serikat itu sendiri. Ini mungkin sedikit membingungkan, tetapi berguna untuk menggambarkan kasus penggabungan.

Berikut adalah contoh id yang buruk

gambar

Dan di sini adalah bagaimana sesuatu seperti ini terlihat ketika menggunakan pengenal tag yang cocok dengan register serikat, yang, sebagaimana disebutkan sebelumnya, sangat valid

 type LabelUnionType = Int of int | String of string 

Membangun asosiasi yang ditandai


Lantas bagaimana membangun kasus unifikasi? Nah, ada berbagai cara, Anda bisa menggunakan salah satu pendekatan berikut:

 let currentLabelUnionType1 = 13 printfn "let currentLabelUnionType1 = 13" printfn "%O" currentLabelUnionType1 let currentLabelUnionType2 = Int 23 printfn "let currentLabelUnionType2 = Int 23" printfn "%O" currentLabelUnionType2 printfn "%A" currentLabelUnionType2 let currentLabelUnionType3 = "Cat" printfn "let currentLabelUnionType3 = \"Cat\"" printfn "%O" currentLabelUnionType3 printfn "%A" currentLabelUnionType3 let currentLabelUnionType4 = String "Cat" printfn "let currentLabelUnionType4 = String \"Cat\"" printfn "%O" currentLabelUnionType4 printfn "%A" currentLabelUnionType4 

Yang saat startup dapat memberikan hasil berikut ketika diluncurkan melalui fungsi printfn (Saya menggunakan formatter% A atau% O printfn di bawah):

gambar

Anda dapat menggunakan hampir semua jenis dalam menggabungkan kasus seperti

  • Tuple
  • dokumentasi
  • Jenis lainnya

Satu-satunya aturan adalah bahwa jenisnya harus ditentukan sebelum kasing serikat Anda dapat menggunakannya.

Berikut adalah contoh yang menggunakan jenis tuple dalam kasus penyatuan:

 type unionUsingTuples = CCY of (int * String) | Rates of (int * decimal) ..... ..... let tupledUnion = (12, "GBP") 

Serikat Kosong


Anda juga dapat menggunakan serikat kosong. Yang mana Anda tidak menentukan tipe. Ini membuat mereka jauh lebih mirip dengan nilai enumerasi .NET standar. Berikut ini sebuah contoh:

 type Player = Cross | Nought .... .... let emptyUnion = Cross 

Bagaimana dengan kasus serupa berdasarkan jenis


Mata elang, seperti Anda, dapat melihat masalahnya. Apa yang akan terjadi jika kita memiliki sesuatu seperti ini:

 type PurchaseOrders = Orders of (string * int) | Empty type ClientOrders = Orders of (string * int) | Empty 

Ini menyebabkan masalah bagi kita, bukan? Bagaimana kita membedakan jenis aliansi yang dibatasi ini? Untungnya, kita dapat mengambil pendekatan yang sepenuhnya memenuhi syarat untuk ini, jadi kita bisa melakukannya, dan semuanya akan bekerja seperti yang diharapkan. Perlu dicatat bahwa Anda dapat mengambil satu langkah lebih jauh dan memasukkan nama modul jika modul terlibat (kita akan mempelajari lebih lanjut tentang ini nanti di artikel berikutnya):

 let purchaseOrders = PurchaseOrders.Orders ("box of 100 scrubbing brushes", 1) let clientOrders = ClientOrders.Orders ("scrubbing brush", 23) 

Perbandingan


Seperti banyak jenis F #, gabungan terbatas dianggap sama jika

  • Panjang kotak gabungannya sama.
  • Jenis kasus serikat mereka cocok.
  • Nilai kasus asosiasi mereka sama.

Tidak sama


Berikut adalah contoh di mana kesetaraan tidak dihormati:

 let purchaseOrders1 = PurchaseOrders.Orders ("box of 100 scrubbing brushes", 1) let purchaseOrders2 = PurchaseOrders.Orders ("10 pack of disks", 1) printfn "purchaseOrders1 = purchaseOrders2 %A" (purchaseOrders1 = purchaseOrders2) 

gambar

Sama


Ini adalah contoh kesetaraan. Ini adalah sesuatu seperti kode .NET biasa, Anda tahu, jika para anggotanya sama, mereka memiliki nilai yang sama dan nomor yang benar, maka ini hampir sama (jika kita mengabaikan kode hash yang ada):

 let purchaseOrders1 = PurchaseOrders.Orders ("box of 100 scrubbing brushes", 1) let purchaseOrders2 = PurchaseOrders.Orders ("box of 100 scrubbing brushes", 1) printfn "purchaseOrders1 = purchaseOrders2 %A" (purchaseOrders1 = purchaseOrders2) 

gambar

Perlu dicatat bahwa kita tidak dapat menggunakan kesetaraan ketika kita harus sepenuhnya memenuhi syarat jenis serikat, karena mereka adalah jenis yang berbeda, jadi ini tidak akan berfungsi:

gambar

Pola Perbandingan


Di bawah ini adalah fungsi kecil yang mengambil Kartu union dan menampilkan kasus-kasus serikat itu dipanggil, dan hanya mengembalikan Unit (batal, jika Anda ingat ini dari artikel sebelumnya dalam seri ini):

 type Card = ValueCard of int | Jack | Queen | King | Ace .... .... let cardFunction card = match card with | ValueCard i -> printfn "its a value card of %A" i | Jack -> printfn "its a Jack" | Queen -> printfn "its a Jack" | King -> printfn "its a Jack" | Ace -> printfn "its a Ace" () //return unit //shows you how to pass it in without a Let binding do cardFunction (Card.ValueCard 8) //or you could use explicit Let binding if you do desire let aceCard = Ace do cardFunction aceCard 

gambar

Jadi persis apa yang terjadi di balik layar


Jadi sekarang kita telah melihat beberapa contoh cara kerja asosiasi berlabel. Jadi, apa yang menurut Anda dapat terjadi jika kami memiliki pustaka F # yang menggunakan asosiasi markup, dan kami memutuskan untuk menggunakannya dari C # / VB.NET. Apakah Anda pikir ini akan berhasil? Jawab: Saya yakin itu akan terjadi. Saya akan melakukan seluruh posting tentang Interop di suatu tempat di masa depan, tapi saya pikir mungkin menarik untuk mempertimbangkan beberapa hal ini sekarang untuk bergabung dengan label, karena mereka sangat berbeda dari semua yang kita lihat dalam pemrograman standar .NET.

Jadi, mari kita ambil Kartu di atas, yang merupakan kode ini:

 type Card = ValueCard of int | Jack | Queen | King | Ace 

Dan jalankan melalui decompiler seperti Reflector / DotPeek (semua yang Anda miliki). Saya menggunakan DotPeek dan mendapatkan kode C # ini untuk baris F # ini. Jadi, seperti yang Anda lihat, kompiler F # melakukan pekerjaan yang bagus untuk memastikan bahwa tipe F # akan bekerja dengan baik dengan .NET, seperti C # / VB.NET.

 using Microsoft.FSharp.Core; using System; using System.Collections; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; [CompilationMapping(SourceConstructFlags.Module)] public static class Program { [EntryPoint] public static int main(string[] argv) { return 0; } [DebuggerDisplay("{__DebugDisplay(),nq}")] [CompilationMapping(SourceConstructFlags.SumType)] [Serializable] [StructLayout(LayoutKind.Auto, CharSet = CharSet.Auto)] public class Card : IEquatable<Program.Card>, IStructuralEquatable, IComparable<Program.Card>, IComparable, IStructuralComparable { [CompilerGenerated] [DebuggerNonUserCode] [DebuggerBrowsable(DebuggerBrowsableState.Never)] public int Tag { [DebuggerNonUserCode] get { return this._tag; } } [CompilerGenerated] [DebuggerNonUserCode] [DebuggerBrowsable(DebuggerBrowsableState.Never)] public bool IsValueCard { [DebuggerNonUserCode] get { return this.get_Tag() == 0; } } [CompilerGenerated] [DebuggerNonUserCode] [DebuggerBrowsable(DebuggerBrowsableState.Never)] public static Program.Card Jack { [CompilationMapping(SourceConstructFlags.UnionCase, 1)] get { // ISSUE: reference to a compiler-generated field return Program.Card._unique_Jack; } } [CompilerGenerated] [DebuggerNonUserCode] [DebuggerBrowsable(DebuggerBrowsableState.Never)] public bool IsJack { [DebuggerNonUserCode] get { return this.get_Tag() == 1; } } [CompilerGenerated] [DebuggerNonUserCode] [DebuggerBrowsable(DebuggerBrowsableState.Never)] public static Program.Card Queen { [CompilationMapping(SourceConstructFlags.UnionCase, 2)] get { // ISSUE: reference to a compiler-generated field return Program.Card._unique_Queen; } } [CompilerGenerated] [DebuggerNonUserCode] [DebuggerBrowsable(DebuggerBrowsableState.Never)] public bool IsQueen { [DebuggerNonUserCode] get { return this.get_Tag() == 2; } } [CompilerGenerated] [DebuggerNonUserCode] [DebuggerBrowsable(DebuggerBrowsableState.Never)] public static Program.Card King { [CompilationMapping(SourceConstructFlags.UnionCase, 3)] get { // ISSUE: reference to a compiler-generated field return Program.Card._unique_King; } } [CompilerGenerated] [DebuggerNonUserCode] [DebuggerBrowsable(DebuggerBrowsableState.Never)] public bool IsKing { [DebuggerNonUserCode] get { return this.get_Tag() == 3; } } [CompilerGenerated] [DebuggerNonUserCode] [DebuggerBrowsable(DebuggerBrowsableState.Never)] public static Program.Card Ace { [CompilationMapping(SourceConstructFlags.UnionCase, 4)] get { // ISSUE: reference to a compiler-generated field return Program.Card._unique_Ace; } } [CompilerGenerated] [DebuggerNonUserCode] [DebuggerBrowsable(DebuggerBrowsableState.Never)] public bool IsAce { [DebuggerNonUserCode] get { return this.get_Tag() == 4; } } static Card() { } [CompilationMapping(SourceConstructFlags.UnionCase, 0)] public static Program.Card NewValueCard(int item) { return (Program.Card) new Program.Card.ValueCard(item); } [CompilationMapping(SourceConstructFlags.UnionCase, 1)] public static Program.Card get_Jack() { // ISSUE: reference to a compiler-generated field return Program.Card._unique_Jack; } [CompilationMapping(SourceConstructFlags.UnionCase, 2)] public static Program.Card get_Queen() { // ISSUE: reference to a compiler-generated field return Program.Card._unique_Queen; } [CompilationMapping(SourceConstructFlags.UnionCase, 3)] public static Program.Card get_King() { // ISSUE: reference to a compiler-generated field return Program.Card._unique_King; } [CompilationMapping(SourceConstructFlags.UnionCase, 4)] public static Program.Card get_Ace() { // ISSUE: reference to a compiler-generated field return Program.Card._unique_Ace; } public static class Tags { public const int ValueCard = 0; public const int Jack = 1; public const int Queen = 2; public const int King = 3; public const int Ace = 4; } [DebuggerTypeProxy(typeof (Program.Card.ValueCard\u0040DebugTypeProxy))] [DebuggerDisplay("{__DebugDisplay(),nq}")] [Serializable] [SpecialName] public class ValueCard : Program.Card { [CompilationMapping(SourceConstructFlags.Field, 0, 0)] [CompilerGenerated] [DebuggerNonUserCode] public int Item { [DebuggerNonUserCode] get { return this.item; } } } [SpecialName] internal class ValueCard\u0040DebugTypeProxy { [CompilationMapping(SourceConstructFlags.Field, 0, 0)] [CompilerGenerated] [DebuggerNonUserCode] public int Item { [DebuggerNonUserCode] get { return this._obj.item; } } } } } 

Kasing rekursif (struktur pohon)


Gabungan berlabel juga dapat digunakan dengan cara rekursif, di mana serikat itu sendiri dapat digunakan sebagai salah satu jenis dalam satu atau lebih kasus. Ini membuat sambungan yang ditandai sangat cocok untuk memodelkan struktur pohon, seperti:

  • Ekspresi matematika
  • Pohon sintaksis abstrak
  • Xml

Bahkan, MSDN memiliki beberapa contoh bagus.

Dalam kode berikut, serikat berlabel rekursif digunakan untuk membuat struktur data pohon biner. Serikat terdiri dari dua kasus: Node, yang merupakan simpul dengan nilai integer dan sub pohon kiri dan kanan, dan Tip, yang melengkapi pohon.

Struktur pohon untuk myTree dalam contoh di bawah ini ditunjukkan pada gambar di bawah ini:

gambar

Dan ini adalah bagaimana kita bisa memodelkan myTree menggunakan gabungan yang ditandai. Perhatikan bagaimana kami mengklasifikasikan bergabung yang ditandai sebagai salah satu kasus bergabung. Dalam hal ini, kasus-kasus menggabungkan keduanya

  • Kiat (serikat kosong, bertindak sebagai enumerasi standar dalam .NET)
  • Atau 3 digit tuple dari angka, Tree, Tree

Juga harus dicatat bahwa fungsi sumTree ditandai dengan kata kunci rec. Apa yang dilakukan mantra sihir ini dengan fungsi kita? Nah, ini menandai fungsi sumTree sebagai fungsi yang akan dipanggil secara rekursif. Tanpa kata kunci "rec" dalam fungsi sumTree, kompiler F # akan mengeluh. Dalam hal ini, kompiler akan melempar kesalahan berikut.

gambar

Tetapi kami adalah orang baik, dan kami akan menggunakan kata kunci yang tepat untuk mendukung kasus penggunaan kami, jadi kami melanjutkan

 type Tree = | Tip | Node of int * Tree * Tree .... .... .... .... let rec sumTree tree = match tree with | Tip -> 0 | Node(value, left, right) -> value + sumTree(left) + sumTree(right) let myTree = Node(0, Node(1, Node(2, Tip, Tip), Node(3, Tip, Tip)), Node(4, Tip, Tip)) let resultSumTree = sumTree myTree printfn "Value of sumTree is %A" resultSumTree 

gambar

MSDN juga memiliki contoh bagus lainnya yang menurut saya layak untuk dicuri (ya, saya berbicara terus terang tentang hal itu sekarang. Saya pikir sementara kalian mengambil sesuatu dari contoh pinjaman ini, seperti Saya jelas mengatakan, saya meminjam, saya tidak dalam bisnis). Mari kita lihat contoh ini di sini:

 type Expression = | Number of int | Add of Expression * Expression | Multiply of Expression * Expression | Variable of string .... .... .... let rec Evaluate (env:Map<string,int>) exp = match exp with | Number n -> n | Add (x, y) -> Evaluate env x + Evaluate env y | Multiply (x, y) -> Evaluate env x * Evaluate env y | Variable id -> env.[id] let environment = Map.ofList [ "a", 1 ; "b", 2 ; "c", 3 ] // Create an expression tree that represents // the expression: a + 2 * b. let expressionTree1 = Add(Variable "a", Multiply(Number 2, Variable "b")) // Evaluate the expression a + 2 * b, given the // table of values for the variables. let result = Evaluate environment expressionTree1 printfn "Value of sumTree is %A" result 

gambar

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


All Articles