F # 8: الاتحادات التمييزية

لذلك تستمر رحلتنا F #. لقد بحثنا في بعض الأنواع الأساسية لبنات البناء ، مثل السجلات / المجموعات ، فقد حان الوقت الآن لإلقاء نظرة على الارتباطات المميزة.

توفر النقابات المصنّفة الدعم للقيم ، والتي يمكن أن تكون واحدة من عدة قيم محتملة. تُعرف القيم المحتملة باسم "الحالات المدمجة" ، وتأخذ النموذج الموضح أدناه:

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

لا تقلق إذا كان بناء الجملة هذا يبدو مخيفًا ، فإن ما يميزه حقًا هو وجود تسمية بحيث يمكن التعرف على كل حالة (تمييز) عن الحالات الأخرى ، ونوع حالة الجمع. الاسم يحتوي على قواعد معينة ، مثل

  • يجب أن تبدأ بحرف كبير
  • يمكن أن يكون معرفًا ، بما في ذلك اسم نوع الاتحاد. قد يكون هذا مربكًا بعض الشيء ، لكن من المفيد وصف حالة الدمج.

هنا مثال لمعرف سيء

صورة

وهنا كيف يمكن أن يبدو شيء من هذا القبيل عند استخدام معرف علامة يطابق سجل الاتحاد ، والذي ، كما ذكر سابقًا ، صالح تمامًا

 type LabelUnionType = Int of int | String of string 

بناء الجمعيات الموسومة


فكيف لبناء حالة التوحيد؟ حسنًا ، هناك طرق مختلفة ، يمكنك استخدام أحد الأساليب التالية:

 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 

والتي عند بدء التشغيل يمكن أن تعطي النتائج التالية عند إطلاقها من خلال وظيفة printfn (يمكنني استخدام٪ A أو٪ O printfn المنسق أدناه):

صورة

يمكنك استخدام أي نوع إلى حد كبير في الجمع بين مثل الحالات

  • قطار
  • الوثائق
  • أنواع أخرى

والقاعدة الوحيدة هي أنه يجب تحديد النوع قبل أن تستخدمه حالة الاتحاد الخاصة بك.

فيما يلي مثال يستخدم نوع tuple في حالات الاتحاد:

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

النقابات الفارغة


يمكنك أيضًا استخدام النقابات الفارغة. وهي تلك التي لا تحدد فيها النوع. هذا يجعلها أكثر تشابهًا مع قيم التعداد .NET القياسية. هنا مثال:

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

ماذا عن حالات مماثلة حسب النوع


يمكن لعين النسر ، مثلك ، رؤية المشكلة. ماذا سيحدث إذا كان لدينا شيء مثل هذا:

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

هذا يسبب لنا مشاكل ، أليس كذلك. كيف نميز بين هذه الأنواع من التحالفات المرسومة؟ لحسن الحظ ، يمكننا اتباع نهج مؤهل بالكامل لهذا ، حتى نتمكن من القيام بذلك ، وكل شيء سوف يعمل كما هو متوقع. تجدر الإشارة إلى أنه يمكنك اتخاذ خطوة أخرى إلى الأمام وتضمين اسم الوحدة النمطية إذا كانت الوحدة متورطة (سنتعلم المزيد عن هذا لاحقًا في المقالة التالية):

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

مقارنة


كما هو الحال مع العديد من أنواع F # ، تعتبر الصلات المحددة متساوية فقط إذا

  • طول قضيتهم مجتمعة هو نفسه.
  • تطابق أنواع حالات اتحادهم.
  • قيم حالات تكوين الجمعيات هي نفسها.

غير متساو


فيما يلي مثال حيث لا تحترم المساواة:

 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) 

صورة

هي


هنا مثال على المساواة. هذا يشبه رمز .NET المعتاد ، كما تعلمون ، إذا كان الأعضاء متماثلين ، لديهم نفس القيم ورقمهم الصحيح ، فسيكون هذا هو نفسه تقريبًا (إذا تجاهلنا رموز التجزئة):

 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) 

صورة

تجدر الإشارة إلى أنه لا يمكننا استخدام المساواة عندما يتعين علينا تأهيل أنواع النقابات تمامًا ، نظرًا لأنها أنواع مختلفة ، لذلك لن ينجح ذلك:

صورة

أنماط المقارنة


فيما يلي وظيفة صغيرة تأخذ اتحاداً للبطاقة وتعرض حالات الاتحاد التي تم استدعاؤها بها ، وتقوم ببساطة بإرجاع الوحدة (باطلة ، إذا كنت تتذكر هذا من المقالات السابقة في هذه السلسلة):

 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 

صورة

لذلك بالضبط ما يحدث وراء الكواليس


لقد رأينا الآن بعض الأمثلة على كيفية عمل الجمعيات التي تحمل علامات. إذاً ، ما الذي يمكن أن يحدث ، برأيك ، إذا كانت لدينا مكتبة F # تستخدم صلات ترميز وقمنا باستخدامها من C # / VB.NET. هل تعتقد أن هذا سوف ينجح. الجواب: أنا متأكد من أنه سيكون. سوف أقوم بنشر منشور كامل حول Interop في مكان ما في المستقبل ، لكنني اعتقدت أنه قد يكون من المثير للاهتمام التفكير في بعض من هذا في الوقت الحالي بالنسبة للوصلات ذات العلامات المميزة ، لأنها مختلفة تمامًا عن كل شيء نراه في برمجة .NET القياسية.

لذلك ، دعونا نأخذ البطاقة أعلاه ، والتي كانت هذه الكود:

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

وتشغيله من خلال decompiler مثل Reflector / DotPeek (كل ما لديك). لقد استخدمت DotPeek وحصلت على رمز C # لهذا السطر F # مفرد. لذلك ، كما ترون ، قام المترجم F # بعمل رائع للتأكد من أن أنواع F # ستعمل بشكل جيد مع .NET العادية ، مثل 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; } } } } } 

الحالات العودية (هياكل الأشجار)


يمكن أيضًا استخدام الوصلات المصنفة بطريقة متكررة ، حيث يمكن استخدام الاتحاد نفسه كواحد من الأنواع في حالة واحدة أو أكثر. هذا يجعل الوصلات المميزة مناسبة جدًا لنمذجة هياكل الأشجار ، مثل:

  • التعبيرات الرياضية
  • بناء جملة الأشجار
  • أكس

في الواقع ، لدى MSDN بعض الأمثلة الجيدة.

في التعليمة البرمجية التالية ، يتم استخدام اتحاد ذو علامة تكرارية لإنشاء بنية بيانات شجرة ثنائية. يتكون الاتحاد من حالتين: Node ، وهي عقدة ذات قيمة عدد صحيح وشجرة فرعية الأيمن والأيسر ، و Tip ، والتي تكمل الشجرة.

يظهر الشكل التالي لبنية شجرة myTree في المثال أدناه:

صورة

وهذه هي الطريقة التي يمكن أن نمثلها في myTree باستخدام روابط مميزة. لاحظ كيف نقوم بتصنيف صلة الترميز كأحد حالات الربط. في هذه الحالة ، حالات الجمع أيضا

  • نصيحة (اتحاد فارغ ، بمثابة تعداد قياسي في .NET)
  • أو مجموعة مكونة من 3 أرقام من رقم ، شجرة ، شجرة

تجدر الإشارة أيضًا إلى أن الدالة sumTree مميزة بالكلمة الرئيسية rec. ماذا تفعل هذه السحر الإملائي مع وظيفتنا؟ حسنًا ، هذا يمثل وظائف sumTree كتلك التي سيتم استدعاءها بشكل متكرر. بدون الكلمة الأساسية "rec" في دالة sumTree ، سوف يشكو المترجم F #. في هذه الحالة ، سيقوم المترجم بإلقاء الخطأ التالي.

صورة

لكننا رجال طيبون ، وسوف نستخدم الكلمات الرئيسية المناسبة لدعم حالة الاستخدام الخاصة بنا ، لذلك نواصل

 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 

صورة

يوجد لدى MSDN أيضًا مثال جيد آخر أعتقد أنه سيكون من المفيد السرقة (نعم ، أنا أتحدث بصراحة حوله الآن. أعتقد أنه بينما تقوم أنت / شباب باستخراج شيء من هذا المثال المقترض ، مثل أنا أقول بوضوح ، أنا مقترض ، أنا لست في مجال الأعمال). لنلقِ نظرة على هذا المثال هنا:

 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 

صورة

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


All Articles