مصطلح بروتوكولات التكرار هو محاولة مني لتعريب iteration protocols، وهي إمكانية موجودة في لغات برمجة كثيرة مثل Java, #C، بايثون، وغيرها. (كلمة iteration معناها حرفياً تكرار، لكني لست متأكداً من هذا التعريب وأريد أن أراجعه).
هذه الإمكانية هي التي تسمح لك أن تقول مثلاً في سي شارب:
foreach(int x in p) {
// code
}
ثم تجد هذه الحلقة التكرارية تعمل مع أي نوع بيانات: مصفوفة، شجرة، قائمة، ...الخ، وتسمح لك أيضاً أن تجعل فصائلك الخاصة تدعم حلقات foreach بشرط أن تلتزم فصيلتك تلك بمواصفات معينة (بروتوكول).
وقد أضفت iteration protocol في كلمات (في نسخة قيد التطوير ولم تصدر بعد، لكن أتمنى طرحها قبل نهاية فبراير)، وهنا أتكلم عن هذه الإضافة.
أولاً، صار في كلمات حلقة لكل/في:
م = [1، 2، 3، 4]
لكل أ في م :
اطبع أ
تابع
هذه الإمكانية تسمح بالمرور على عناصر المصفوفات، والنصوص، والقواميس. في حالة القاموس يكون العنصر الذي يأتيك هو كائن به خاصية اسمها مفتاح وخاصية اسمها قيمة ، ويمكن استخدامه هكذا:
ق = {"حار" => "بارد"، "كثير" => "قليل"، "قوي" => "ضعيف"}
لكل أ في ق :
اطبع "عكس "، مفتاح أ، " هو "، قيمة أ
تابع
بالنسبة للبروتوكول نفسه: لو أردت أن تصمم فصيلة بحيث يمكن المرور على عناصر كائناتها بأمر لكل/في، فستحتاج فصيلتك أن يكون فيها رد (أي method تعود بقيمة) اسمه المعدد ، يعود بكائن خاص تستخدمه حلقة لكل. كيف تستخدمه بالضبط؟ لو قلت مثلاً:
لكل أ في ب:
-- أوامر
تابع
فإن الأمر يتم تحويله داخلياً إلى ما يشبه الآتي:
م = ب : المعدد()
كرر مادام م : تقدم()
أ = م : القيمة.الحالية()
-- أوامر
تابع
إذاً لكي يعمل المعدد لابد أن يكون فيه ردين، اسمهما تقدم و القيمة.الحالية ، ويحققان معاً الشروط الآتية:
- في البداية يكون المعدد "قبل أول عنصر"، معنى هذا ببساطة أننا في تلك الحالة لو نادينا تقدم وكان هناك عناصر، سنصبح عند أول عنصر.
- تقدم تحاول الذهاب للعنصر الذي عليه الدور، تعود بالقيمة صحيح في حالة وجود عنصر وإلا تعود بالقيمة خطأ
- القيمة.الحالية تعود بالعنصر الحالي (الذي وصلنا إليه عن طريق تقدم)، إن كان هناك عنصراً موجوداً.
سوف يلاحظ مبرمجو لغة سي شارب أن البروتوكول الخاص بكلمات هو نفسه بروتوكول تلك اللغة. نعم هذا صحيح :)
لكن هناك شيء خاص بكلمات هنا: القنوات تدعم بروتوكولات التكرار.
هذا شيء مهم جداً، لذلك سأكرره: القنوات تدعم بروتوكولات التكرار!
(إن كنت لا تعرف بعد ما هي القنوات، يمكنك أن تطلع على
هذا الجزء من توثيق كلمات الخاص بالبرمجة المتوازية)
ما أهمية هذه المعلومة على أية حال؟ سأقول لك: هذا يجعل أشياء برمجية كثيرة سهلة للغاية. يمكنك أن تشغل إجراءاً موازياً يقرأ البيانات من أي مصدر (قائمة ملفات، من الإنترنت، أو حتى من قنوات أخرى) ويرسل كل ما يقرأه إلى قناة، ثم في البرنامج الرئيسي تقوم بالقراءة من القناة بواسطة أمر لكل/في.
مثال بسيط على هذا، المرور على عناصر شجرة: تخيل أن لدينا فصيلة اسمها شجرة لها ثلاث خصائص: بيان، يمين، يسار. هذه هي الشجرة الثنائية العادية التي يأخذها طلبة علوم الحاسب. الآن نريد أن نفعل هذا:
لكل أ في تعديد.شجرة(ش):
....
تابع
سوف نكتب الآن الدالة تعديد.شجرة (كلمة تعديد هي تعريبي لكلمة enumeration):
دالة تعديد.شجرة(ش):
ق = قناة()
شغل تعديد(ش، ق)
ارجع ب: ق
نهاية
كما ترى، كل ما تفعله هذه الدالة هو صنع قناة، ثم تشغيل إجراء مواز وتقديم الشجرة الأصلية والقناة له. وظيفة ذلك الإجراء (المسمى تعديد) هو المرور على عناصر الشجرة وإرسال كل ما يمر عليه للقناة:
إجراء تعديد(ش، ق):
إذا ش <> لاشيء :
تعديد(يسار ش، ق)
ارسل بيان ش إلى ق
تعديد(يمين ش، ق)
تم
نهاية
الشيء الذي يميز هذا الإجراء هو أنه لا يميزه شيء! إنه إجراء tree traversal عادي جداً جداً - مرة أخرى، مثل ما يأخذه طلبة حاسبات - كل ما يفعله هو أنه حين يجد قيمة يرسلها للقناة، ولا يوجد أي شيء عن التعديد أو البروتوكولات، لكن هذا الإجراء هو كل ما تحتاجه للمرور على عناصر الشجرة.
ماذا لو أردت المرور بطريقة مختلفة مثل breadth-first أو ربما postorder؟ غيّر الإجراء الثاني بدون أن تهتم بالبروتوكلات وما إلى ذلك. ماذا لو أردت المرور على عناصر شيء آخر؟ ربما graph؟ استخدم الخوارزميات التي تعرفها، لكن لا تنس إرسال كل عنصر للقناة :)
توجد إمكانيات مشابهة في لغة سي شارب (أمر yield) وفي لغة بايثون (إمكانية generators) لكني أعتقد أن طريقة كلمات أقوى (المرور على الشجرة مثلاً أعقد في سي شارب/بايثون لأن أمر yield لا يعمل بنفس الطريقة في وجود recursion).
عند تصميم لغة برمجة لا يكفي أن تضع فيها باقة من الإمكانيات، ولكن لابد من التفكير في التكامل بين الإمكانيات المختلفة. أعتقد أن التكامل بين الإجراءات الموازية والقنوات وبروتوكولات التكرار يعطي كلمات قوة خاصة.