مجموعة من الاحتمالات: دليل لمطابقة أنماط روبي

نشرت: 2022-03-11

مطابقة الأنماط هي الميزة الجديدة الكبيرة القادمة إلى Ruby 2.7. لقد تم الالتزام بالجذع بحيث يمكن لأي شخص مهتم تثبيت Ruby 2.7.0-dev والتحقق من ذلك. يُرجى الأخذ في الاعتبار أنه لم يتم الانتهاء من أيٍّ من هذه التعليقات وأن فريق التطوير يبحث عن التعليقات ، لذا إذا كان لديك أي تعليقات ، فيمكنك إخبار الملتزمون قبل أن يتم إيقاف الميزة فعليًا.

آمل أن تفهم ماهية مطابقة الأنماط وكيفية استخدامها في Ruby بعد قراءة هذا المقال.

ما هي مطابقة الأنماط؟

مطابقة الأنماط هي ميزة توجد بشكل شائع في لغات البرمجة الوظيفية. وفقًا لوثائق Scala ، فإن مطابقة النمط هي "آلية لفحص القيمة مقابل النمط. يمكن للمطابقة الناجحة أيضًا تفكيك القيمة إلى الأجزاء المكونة لها ".

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

وبسبب ذلك ، فإن مطابقة الأنماط تركت انطباعًا عني حقًا. هذا ما تبدو عليه مطابقة الأنماط في الإكسير:

 [a, b, c] = [:hello, "world", 42] a #=> :hello b #=> "world" c #=> 42

المثال أعلاه يشبه إلى حد كبير مهمة متعددة في Ruby. ومع ذلك ، فهو أكثر من ذلك. يتحقق أيضًا مما إذا كانت القيم متطابقة أم لا:

 [a, b, 42] = [:hello, "world", 42] a #=> :hello b #=> "world"

في الأمثلة أعلاه ، الرقم 42 على الجانب الأيسر ليس متغيرًا تم تعيينه. إنها قيمة للتحقق من أن نفس العنصر في ذلك الفهرس المعين يطابق العنصر الموجود في الجانب الأيمن.

 [a, b, 88] = [:hello, "world", 42] ** (MatchError) no match of right hand side value

في هذا المثال ، بدلاً من القيم التي يتم تعيينها ، يتم رفع MatchError بدلاً من ذلك. هذا لأن الرقم 88 لا يتطابق مع الرقم 42.

يعمل أيضًا مع الخرائط (التي تشبه التجزئة في Ruby):

 %{"name": "Zote", "title": title } = %{"name": "Zote", "title": "The mighty"} title #=> The mighty

يتحقق المثال أعلاه من أن قيمة name المفتاح هي Zote ، ويربط قيمة عنوان المفتاح title المتغير.

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

علاوة على ذلك ، فإنه يسمح أيضًا للغة المكتوبة ديناميكيًا مثل Elixir بالحمل الزائد للأسلوب:

 def process(%{"animal" => animal}) do IO.puts("The animal is: #{animal}") end def process(%{"plant" => plant}) do IO.puts("The plant is: #{plant}") end def process(%{"person" => person}) do IO.puts("The person is: #{person}") end

اعتمادًا على مفتاح تجزئة الوسيطة ، يتم تنفيذ طرق مختلفة.

نأمل أن يوضح لك ذلك مدى قوة مطابقة الأنماط. هناك العديد من المحاولات لجلب مطابقة الأنماط إلى روبي بأحجار كريمة مثل noaidi و qo و egison-ruby.

يحتوي Ruby 2.7 أيضًا على تطبيقه الخاص الذي لا يختلف كثيرًا عن هذه الأحجار الكريمة ، وهذه هي الطريقة التي يتم بها حاليًا.

بناء جملة مطابقة نمط روبي

تتم مطابقة الأنماط في روبي من خلال بيان case . ومع ذلك ، فبدلاً من استخدام الكلمة الأساسية when استخدامها ، يتم استخدام الكلمة الأساسية in بدلاً من ذلك. كما أنه يدعم استخدام عبارات if or unless :

 case [variable or expression] in [pattern] ... in [pattern] if [expression] ... else ... end

يمكن أن يقبل بيان الحالة متغيرًا أو تعبيرًا وسيتم مطابقته مع الأنماط المتوفرة في الفقرة. يمكن أيضًا تقديم عبارات if أو إلا بعد النمط. يستخدم فحص المساواة هنا أيضًا === مثل بيان الحالة العادية. هذا يعني أنه يمكنك مطابقة مجموعات فرعية ومثيل من الفئات. فيما يلي مثال على كيفية استخدامه:

مطابقة المصفوفات

 translation = ['th', 'เต้', 'ja', 'テイ'] case translation in ['th', orig_text, 'en', trans_text] puts "English translation: #{orig_text} => #{trans_text}" in ['th', orig_text, 'ja', trans_text] # this will get executed puts "Japanese translation: #{orig_text} => #{trans_text}" end

في المثال أعلاه ، تتم مطابقة translation المتغيرة مع نمطين:

['th', orig_text, 'en', trans_text] و ['th', orig_text, 'ja', trans_text] . ما يفعله هو التحقق مما إذا كانت القيم الموجودة في النموذج تتطابق مع القيم الموجودة في متغير translation في كل من المؤشرات. إذا كانت القيم متطابقة ، فإنها تقوم بتعيين القيم في متغير translation إلى المتغيرات في النمط في كل من المؤشرات.

الرسوم المتحركة لمطابقة أنماط روبي: مصفوفات مطابقة

تجزئة مطابقة

 translation = {orig_lang: 'th', trans_lang: 'en', orig_txt: 'เต้', trans_txt: 'tae' } case translation in {orig_lang: 'th', trans_lang: 'en', orig_txt: orig_txt, trans_txt: trans_txt} puts "#{orig_txt} => #{trans_txt}" end

في المثال أعلاه ، أصبح متغير translation الآن تجزئة. تتم مطابقته مع تجزئة أخرى in الفقرة. ما يحدث هو أن بيان الحالة يتحقق مما إذا كانت جميع المفاتيح في النمط تتطابق مع المفاتيح الموجودة في متغير translation . يتحقق أيضًا من أن جميع القيم لكل تطابق رئيسي. ثم يقوم بتعيين القيم إلى المتغير في التجزئة.

الرسوم المتحركة لمطابقة أنماط روبي: مصفوفات مطابقة

مجموعات فرعية مطابقة

يتبع فحص الجودة المستخدم في مطابقة النمط منطق === .

أنماط متعددة

  • | يمكن استخدامها لتحديد أنماط متعددة لكتلة واحدة.
 translation = ['th', 'เต้', 'ja', 'テイ'] case array in {orig_lang: 'th', trans_lang: 'ja', orig_txt: orig_txt, trans_txt: trans_txt} | ['th', orig_text, 'ja', trans_text] puts orig_text #=> เต้ puts trans_text #=> テイend

في المثال أعلاه ، يتطابق متغير translation مع كلٍّ من {orig_lang: 'th', trans_lang: 'ja', orig_txt: orig_txt, trans_txt: trans_txt} التجزئة و ['th', orig_text, 'ja', trans_text] مجموعة مصفوفة.

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

تعيين السهم

في هذه الحالة ، يمكن استخدام => لتعيين قيمة متطابقة لمتغير.

 case ['I am a string', 10] in [Integer, Integer] => a # not reached in [String, Integer] => b puts b #=> ['I am a string', 10] end

يكون هذا مفيدًا عندما تريد فحص القيم داخل بنية البيانات ولكن أيضًا تربط هذه القيم بمتغير.

مشغل الدبوس

هنا ، يمنع عامل الدبوس المتغيرات من إعادة تعيينها.

 case [1,2,2] in [a,a,a] puts a #=> 2 end

في المثال أعلاه ، تتم مطابقة المتغير a في النموذج مع 1 ، 2 ، ثم 2. سيتم تعيينه إلى 1 ، ثم 2 ، ثم إلى 2. وهذا ليس موقفًا مثاليًا إذا كنت تريد التحقق من أن جميع القيم في المصفوفة هي نفسها.

 case [1,2,2] in [a,^a,^a] # not reached in [a,b,^b] puts a #=> 1 puts b #=> 2 end

عند استخدام عامل التشغيل ، يقوم بتقييم المتغير بدلاً من إعادة تعيينه. في المثال أعلاه ، لا يتطابق [1،2،2] مع [a ، ^ a ، ^ a] لأنه في الفهرس الأول ، تم تعيين a إلى 1. في الفهرس الثاني والثالث ، يتم تقييم a ليكون 1 ، لكنه يقابل 2.

ومع ذلك ، فإن [a ، b ، ^ b] تطابق [1،2،2] نظرًا لأنه تم تعيين a لـ 1 في الفهرس الأول ، تم تعيين b لـ 2 في الفهرس الثاني ، ثم تمت مطابقة ^ b ، الذي أصبح الآن 2 ، مقابل 2 في الفهرس الثالث حتى يمر.

 a = 1 case [2,2] in [^a,^a] #=> not reached in [b,^b] puts b #=> 2 end

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

الشرطة السفلية ( _ ) عامل التشغيل

الشرطة السفلية ( _ ) تستخدم لتجاهل القيم. دعنا نراها في بعض الأمثلة:

 case ['this will be ignored',2] in [_,a] puts a #=> 2 end
 case ['a',2] in [_,a] => b puts a #=> 2 Puts b #=> ['a',2] end

في المثالين أعلاه ، أي قيمة تطابق _ يمر. في حالة الحالة الثانية ، يلتقط عامل التشغيل => القيمة التي تم تجاهلها أيضًا.

حالات الاستخدام لمطابقة الأنماط في روبي

تخيل أن لديك بيانات JSON التالية:

 { nickName: 'Tae' realName: {firstName: 'Noppakun', lastName: 'Wongsrinoppakun'} username: 'tae8838' }

في مشروع Ruby الخاص بك ، تريد تحليل هذه البيانات وعرض الاسم بالشروط التالية:

  1. إذا كان اسم المستخدم موجودًا ، فقم بإرجاع اسم المستخدم.
  2. إذا كان اللقب والاسم الأول واسم العائلة موجودًا ، فقم بإرجاع اللقب والاسم الأول ثم الاسم الأخير.
  3. إذا كان اللقب غير موجود ، ولكن الاسم الأول والأخير موجودان ، فقم بإرجاع الاسم الأول ثم الاسم الأخير.
  4. إذا لم تنطبق أي من الشروط ، فارجع "مستخدم جديد".

هذه هي الطريقة التي سأكتب بها هذا البرنامج في روبي الآن:

 def display_name(name_hash) if name_hash[:username] name_hash[:username] elsif name_hash[:nickname] && name_hash[:realname] && name_hash[:realname][:first] && name_hash[:realname][:last] "#{name_hash[:nickname]} #{name_hash[:realname][:first]} #{name_hash[:realname][:last]}" elsif name_hash[:first] && name_hash[:last] "#{name_hash[:first]} #{name_hash[:last]}" else 'New User' end end

الآن ، دعنا نرى كيف يبدو مع مطابقة الأنماط:

 def display_name(name_hash) case name_hash in {username: username} username in {nickname: nickname, realname: {first: first, last: last}} "#{nickname} #{first} #{last}" in {first: first, last: last} "#{first} #{last}" else 'New User' end end

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

 `{nickname: nickname, realname: {first: first, last: last}}`

بدلا من:

 `name_hash[:nickname] && name_hash[:realname] && name_hash[:realname][:first] && name_hash[:realname][:last]`.

Deconstruct and Deconstruct_keys

هناك طريقتان خاصتان جديدتان يتم تقديمهما في Ruby 2.7: deconstruct و deconstruct_keys . عندما تتم مطابقة مثيل لفئة مع مصفوفة أو تجزئة ، يتم استدعاء مفاتيح deconstruct أو deconstruct_keys ، على التوالي.

سيتم استخدام نتائج هذه الأساليب لمطابقة الأنماط. هنا مثال:

 class Coordinate attr_accessor :x, :y def initialize(x, y) @x = x @y = y end def deconstruct [@x, @y] end def deconstruct_key {x: @x, y: @y} end end

يحدد الكود فئة تسمى Coordinate . لها x و y كصفاتها. كما أن لديها أساليب deconstruct و deconstruct_keys المحددة.

 c = Coordinates.new(32,50) case c in [a,b] pa #=> 32 pb #=> 50 end

هنا ، يتم تعريف مثيل Coordinate ومطابقة النمط مع مصفوفة. ما يحدث هنا هو أنه يتم استدعاء Coordinate#deconstruct ويتم استخدام النتيجة لمطابقة المصفوفة [a,b] المحددة في النمط.

 case c in {x:, y:} px #=> 32 py #=> 50 end

في هذا المثال ، تتم مطابقة نفس مثيل Coordinate مقابل تجزئة. في هذه الحالة ، يتم استخدام نتيجة Coordinate#deconstruct_keys للمطابقة مع التجزئة {x: x, y: y} المحددة في النمط.

ميزة تجريبية مثيرة

بعد تجربة مطابقة النمط لأول مرة في Elixir ، كنت أعتقد أن هذه الميزة قد تتضمن طريقة التحميل الزائد وتنفيذها باستخدام بناء جملة يتطلب سطرًا واحدًا فقط. ومع ذلك ، فإن لغة Ruby ليست لغة تم إنشاؤها مع مراعاة مطابقة الأنماط ، لذلك هذا أمر مفهوم.

من المحتمل أن يكون استخدام تعليمة الحالة طريقة سهلة للغاية لتنفيذ هذا ولا يؤثر أيضًا على الكود الحالي (بصرف النظر عن طرق deconstruct و deconstruct_keys ). إن استخدام بيان الحالة يشبه في الواقع استخدام تطبيق Scala لمطابقة الأنماط.

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