برمجة عامة
أنماط البرمجة |
---|
|
البرمجة العامة (بالإنجليزية: Generic programming) هي نمط من أنماط البرمجة تُكتب فيه الخوارزميات من حيث أنواع البيانات "يتم تحديدها لاحقًا"، ثم تُنشأ "تجسيداتها" عند الحاجة لأنواع محددة تُمرر كوسيط. هذا الأسلوب، الذي بدأ في لغة البرمجة أم أل عام 1973،[1][2] يسمح بكتابة دوال أو أنواع بيانات عامة تختلف فقط في مجموعات الأنواع التي تعمل عليها، مما يقلل من تكرار الكود.
دخلت البرمجة العامة إلى التيار السائد مع أيدا في عام 1977. ومع إدخال قوالب في لغة سي++، أصبحت البرمجة العامة جزءًا أساسيًا من تصميم مكتبات برمجية احترافية. وتم تحسين هذه التقنيات لاحقًا مع إدخال "الأنواع المُعاملة" في الكتاب المؤثر أنماط التصميم (بالإنجليزية: Design Patterns) الصادر عام 1994.[3]
وقدّم أندريه ألكسندرسكو تقنيات جديدة في كتابه تصميم C++ الحديث: البرمجة العامة وأنماط التصميم المطبقة عام 2001، ثم طبقتها لغة دي لاحقًا.
تُعرف هذه الكيانات البرمجية باسم "عامّة" (بالإنجليزية: generics) في لغات مثل أيدا، سي شارب، دلفي، إيفل، إف شارب، جافا، نيم، بايثون، غو، رست، سويفت، تايب سكريبت، وفيجوال بيسك دوت نت. وتُعرف باسم "تعدد الأشكال المُعامِل" (بالإنجليزية: parametric polymorphism) في لغات مثل أم أل، سكالا، جوليا، وهاسكل. (تستخدم هاسكل أيضًا مصطلح "عام" لتمييز مفهوم ذي صلة لكنه مختلف بعض الشيء.)
صاغ المصطلح "البرمجة العامة" في الأصل كل من ديفيد مويسر وألكسندر ستيبانوف[4] ليصف نمطًا برمجيًا تُجرد فيه المتطلبات الأساسية لأنواع البيانات من أمثلة واقعية من الخوارزميات وهياكل البيانات، وتُصاغ كمفاهيم. تُنفّذ الدوال العامة بناءً على هذه المفاهيم، باستخدام آليات التعميم المتوفرة في اللغة كما هو موضح أعلاه.
ستيبانوف–مويسر وأنماط أخرى من البرمجة العامة
[عدل]تُعرّف البرمجة العامة في Musser & Stepanov (1989) على النحو الآتي:
يُعد نمط "البرمجة العامة" نهجًا في تفكيك البرمجيات تُجرد فيه المتطلبات الأساسية للأنواع عبر أمثلة واقعية من الخوارزميات وهياكل البيانات، وتُصاغ كمفاهيم، بطريقة تشبه تجريد النظريات الجبرية في الجبر المجرد.[6] من الأمثلة المبكرة على هذا النهج البرمجي ما نُفذ في لغتي سكيمي وأدا،[7] لكن المثال الأشهر هو مكتبة القوالب المعيارية (STL)،[8][9] التي طورت نظرية المكررات لفصل هياكل البيانات التسلسلية عن الخوارزميات التي تعمل عليها.
فعلى سبيل المثال، إذا كان لدينا N بنى بيانات تسلسلية (مثل قائمة مرتبطة أحادية، أو متجه) وM خوارزميات تعمل عليها (مثل find
أو sort
)، فإن النهج المباشر يتطلب تنفيذ كل خوارزمية لكل بنية بيانات، أي N × M تنفيذًا. أما في البرمجة العامة، فكل بنية بيانات تُرجع نموذجًا لمفهوم المكرر (وهو نوع بيانات بسيط يمكن إلغاء إشارته للحصول على القيمة الحالية أو تعديله للإشارة إلى قيمة أخرى ضمن التسلسل)، وتُكتب كل خوارزمية بشكل عام باستخدام مكررين كمُعاملات يشيرون إلى بداية ونهاية النطاق المطلوب معالجته. وبهذا، لا يتطلب الأمر سوى N + M تنفيذات فقط.
تحدد STL عدة مفاهيم للمكررات، كل منها تنقيح لأخرى أكثر تقييدًا، مثل "المكررات الأمامية" التي تتيح فقط التنقل إلى القيمة التالية (كما في قائمة مرتبطة أحادية أو تدفق بيانات مدخلات)، بينما "مكررات الوصول العشوائي" تتيح أيضًا الوصول إلى أي عنصر في التسلسل بزمن ثابت (كما في المتجهات). ومن المهم أن كل بنية بيانات تُرجع نموذج المفهوم الأكثر عمومية الذي يمكن تنفيذه بكفاءة — إذ تُعد متطلبات تحليل الخوارزميات جزءًا صريحًا من تعريف المفهوم. وهذا يقيّد البنى التي يمكن تطبيق خوارزمية معينة عليها، ما يجعل متطلبات الكفاءة عاملًا حاسمًا في اختيار بنية البيانات المناسبة.
وقد طُبّق نهج البرمجة العامة بالمثل في مجالات أخرى مثل خوارزميات الرسوم البيانية.[10]
على الرغم من أن هذا النهج غالبًا ما يستخدم ميزات اللغة المتعلقة بالعمومية في وقت التصريف والقوالب، إلا أنه مستقل عن التفاصيل التقنية الخاصة بلغة برمجة معينة. كتب رائد البرمجة العمومية ألكسندر ستيبانوف:
أشار بيارن ستروستروب إلى:
تشمل أنماط البرمجة الأخرى التي وُصِفت بأنها برمجة عمومية ما يُعرف باسم "البرمجة العمومية للأنواع البيانية" كما وُصفت في "البرمجة العامة – مقدمة".[14] كما تُعد تخلص من شفرة متداولة منهجًا خفيفًا للبرمجة العمومية في لغة هاسكل.[15]
في هذه المقالة، نُميز بين أنماط نمط برمجة البرمجة العمومية عالية المستوى، كما ورد أعلاه، وبين آليات "العمومية" على مستوى اللغة المستخدمة في تنفيذها (انظر دعم لغات البرمجة للعمومية). لمزيد من النقاش والمقارنة بين أنماط البرمجة العمومية، انظر:[16]
دعم لغات البرمجة للعمومية
[عدل]توجد آليات العمومية في لغات المستوى العالي منذ سبعينيات القرن العشرين على الأقل، في لغات مثل أم أل، نظام برمجة كلو، وأيدا. وقد تبنّتها لاحقًا العديد من لغات كائنية التوجه ومعتمدة على الكائنات، بما في ذلك بيتا، سي++، دي، إيفل، جافا، وتريلس-أول التابعة لديجيتال إكوبمينت والتي أصبحت الآن منحلة.
تُنفذ العمومية وتُدعَم بطرق مختلفة في لغات البرمجة المتنوعة؛ كما استُخدم مصطلح "عمومية" بمعانٍ مختلفة ضمن سياقات برمجية مختلفة. فعلى سبيل المثال، في لغة الفورث، يمكن لـالمصرف تنفيذ الشيفرة أثناء الترجمة، ويمكن إنشاء "كلمات مترجم" جديدة وتنفيذات جديدة لها في الوقت الفعلي. تحتوي اللغة على عدد قليل من "الكلمات" التي تكشف عن سلوك المصرّف، وبالتالي فإنها توفر بطبيعتها قدرات "عمومية"، رغم أن هذا المصطلح لا يُستخدم عادةً في مؤلفات Forth. وبالمثل، فإن اللغات ذات الأنظمة الديناميكية في التصنيف، خصوصًا المفسرة منها، غالبًا ما تقدم "العمومية" افتراضيًا، لأن تمرير القيم إلى الدوال وتعيين القيم لا يعتمدان على نوع معين، ويُستخدم هذا السلوك كثيرًا لأغراض التجريد أو تقليل حجم الشيفرة. غير أن هذا لا يُطلق عليه عادةً "العمومية" لأنه ناتج مباشر عن نظام التصنيف الديناميكي المستخدم في اللغة.[بحاجة لمصدر] يُستخدم المصطلح أيضًا في البرمجة الوظيفية، وخصوصًا في لغات شبيهة بهاسكل، التي تستخدم نظام أنواع بنيوي، حيث تكون الأنواع دومًا معلمّة، والشيفرة الخاصة بتلك الأنواع عمومية. وتؤدي هذه الاستخدامات وظيفة مشابهة من حيث التوفير في الشيفرة وتحقيق التجريد.
يمكن النظر إلى أنواع المصفوفات والستروكت باعتبارها أنواعًا عمومية محددة مسبقًا. فكل استخدام لنوع مصفوفة أو ستروكت يؤدي إلى إنشاء نوع ملموس جديد، أو إعادة استخدام نوع منشأ سابقًا. تُعتبر أنواع عناصر المصفوفات وحقول ستروكت أنواعًا مُعلَّمة تُستخدم لإنشاء النوع العمومي المقابل. غالبًا ما تكون كل هذه الأمور مدمجة داخل المصرّف، وتختلف البُنى النحوية الخاصة بها عن تلك الخاصة بالبُنى العمومية الأخرى. تحاول بعض لغات برمجة قابلة للامتداد أن توحّد بين الأنواع العمومية المدمجة وتلك التي يعرّفها المستخدم.
فيما يلي استعراض واسع لآليات العمومية في لغات البرمجة. وللاطلاع على مقارنة متخصصة بشأن مدى ملاءمة هذه الآليات للبرمجة العمومية، انظر:[17]
في اللغات الكائنية التوجه
[عدل]عند إنشاء أصناف حاوية في لغات ذات تصنيف ثابت، يكون من غير العملي كتابة تنفيذات خاصة بكل نوع بيانات يحتويه الصنف، خاصة إذا كانت الشيفرة لكل نوع متشابهة إلى حد كبير. على سبيل المثال، في لغة C++، يمكن تفادي تكرار الشيفرة بتعريف قالب صنف:
template<typename T>
class List {
// محتوى الصنف
};
List<Animal> list_of_animals;
List<Car> list_of_cars;
في المثال أعلاه، T
هو عنصر نائب يُستبدل بالنوع الذي يُحدَّد عند إنشاء القائمة. هذه "الحاويات من نوع T"، والتي تُعرف عمومًا باسم القالب، تتيح إعادة استخدام الصنف مع أنواع بيانات مختلفة طالما تم احترام بعض العقود مثل الأنواع الفرعية وتوقيع النوع. لا ينبغي الخلط بين هذه الآلية العمومية وبين تعددية الأشكال، التي تشير إلى الاستخدام الخوارزمي للفئات القابلة للاستبدال: على سبيل المثال، قائمة من الكائنات من النوع Moving_Object
تحتوي على كائنات من نوع Animal
وCar
. يمكن أيضًا استخدام القوالب لإنشاء دوال مستقلة عن النوع كما في مثال Swap
أدناه:
// "&" تعني تمرير بالمرجع
template<typename T>
void Swap(T& a, T& b) { // يوجد دالة مشابهة ولكن أكثر أمانًا وسرعة في مكتبة <utility>
T temp = b;
b = a;
a = temp;
}
std::string world = "World!";
std::string hello = "Hello, ";
Swap(world, hello);
std::cout << world << hello << ‘\n’; // الناتج هو "Hello, World!".
يُعتبر بناء template
في لغة C++ كما في المثال أعلاه، من أكثر آليات العمومية شهرةً وانتشارًا، وقد ساهم في ترسيخ مفهوم العمومية بين المبرمجين ومصممي لغات البرمجة. وتدعم لغة D أيضًا قوالب كاملة العمومية استنادًا إلى سابقات C++ ولكن ببنية أبسط. أما لغة جافا، فقد وفرت آليات للعمومية ببنية نحوية مستوحاة من C++ منذ إصدار منصة جافا 5.0.
وقدمت سي شارب 2.0، وأوكسجين 1.5 (سابقًا كروم)، وفيجوال بيسك دوت نت 2005 بناءات تستفيد من دعم العمومية المدمج في دوت نت فريموورك من الإصدار 2.0.
القوالب العامة في أيدا
[عدل]تمتاز أيدا بدعمها للقوالب منذ تصميمها الأول في الفترة بين 1977–1980. وتستخدم المكتبة المعيارية القوالب لتوفير العديد من الخدمات. وقد أضافت أيدا 2005 مكتبة حاويات عامة شاملة إلى المكتبة القياسية، مستوحاة من مكتبة القوالب المعيارية في ++C.
الوحدة القالبية هي حزمة أو برنامج فرعي يستقبل واحداً أو أكثر من "المعلمات الشكلية القالبية".[18]
المعلمة الشكلية القالبية يمكن أن تكون قيمة أو متغيراً أو ثابتاً أو نوعاً أو برنامجاً فرعياً أو حتى مثيلاً لوحدة قالبية أخرى معرفة مسبقاً. وللمعلمات الشكلية القالبية من نوع "نوع"، تميز البنية اللغوية بين الأنواع المنفصلة وأنواع الفاصلة العائمة والثابتة والمؤشرات وغير ذلك. ويمكن أن تحتوي بعض المعلمات الشكلية على قيم افتراضية.
لـ"استنساخ" وحدة قالبية، يقوم المبرمج بتمرير "معلمات فعلية" لكل معلمة شكلية. ويتصرف النسخ القالبي حينها كوحدة عادية تماماً. ومن الممكن استنساخ وحدات قالبية أثناء وقت التشغيل، مثلاً داخل حلقة.
مثال
[عدل]مواصفة حزمة قالبية:
generic
Max_Size : Natural; -- قيمة شكلية قالبية
type Element_Type is private; -- نوع شكلي قالب؛ يقبل أي نوع غير محدود
package Stacks is
type Size_Type is range 0 .. Max_Size;
type Stack is limited private;
procedure Create (S : out Stack;
Initial_Size : in Size_Type := Max_Size);
procedure Push (Into : in out Stack; Element : in Element_Type);
procedure Pop (From : in out Stack; Element : out Element_Type);
Overflow : exception;
Underflow : exception;
private
subtype Index_Type is Size_Type range 1 .. Max_Size;
type Vector is array (Index_Type range <>) of Element_Type;
type Stack (Allocated_Size : Size_Type := 0) is record
Top : Index_Type;
Storage : Vector (1 .. Allocated_Size);
end record;
end Stacks;
استنساخ الحزمة القالبية:
type Bookmark_Type is new Natural;
-- يمثل موقعاً في مستند النص الجاري تعديله
package Bookmark_Stacks is new Stacks (Max_Size => 20,
Element_Type => Bookmark_Type);
-- يسمح للمستخدم بالقفز بين المواقع المسجلة في المستند
استخدام نسخة من حزمة قالبية:
type Document_Type is record
Contents : Ada.Strings.Unbounded.Unbounded_String;
Bookmarks : Bookmark_Stacks.Stack;
end record;
procedure Edit (Document_Name : in String) is
Document : Document_Type;
begin
-- تهيئة مكدس العلامات المرجعية:
Bookmark_Stacks.Create (S => Document.Bookmarks, Initial_Size => 10);
-- ثم فتح الملف Document_Name وقراءته...
end Edit;
المزايا والقيود
[عدل]تتيح بنية اللغة تحديداً دقيقاً للقيود المفروضة على المعلمات الشكلية القالبية. فعلى سبيل المثال، يمكن تحديد أن نوعاً شكلياً قالبيّاً لن يقبل إلا نوعاً معيارياً. كما يمكن التعبير عن قيود بين المعلمات الشكلية القالبية، مثلاً:
generic
type Index_Type is (<>); -- يجب أن يكون نوعاً منفصلاً
type Element_Type is private; -- يمكن أن يكون أي نوع غير محدود
type Array_Type is array (Index_Type range <>) of Element_Type;
في هذا المثال، يتم تقييد النوع Array_Type بالنوعين Index_Type وElement_Type. وعند استنساخ الوحدة، يجب على المبرمج تمرير نوع مصفوفة فعلي يحقق هذه القيود.
من عيوب هذا التحكم الدقيق هو التعقيد في البنية اللغوية، لكن، بما أن جميع المعلمات الشكلية القالبية معرفة بالكامل في المواصفة، فإن المُصرّف يمكنه استنساخ القوالب دون الحاجة إلى الاطلاع على جسم القالب.
وعلى عكس ++C، لا تسمح أيدا بإنشاء نسخ متخصصة للقوالب، وتتطلب أن يتم استنساخ كل قالب بشكل صريح. لهذه القواعد عدة نتائج:
- يمكن للمُصرّف تنفيذ "قوالب مشتركة": يمكن مشاركة الشيفرة الكائنية لوحدة قالبية بين كل النسخ (ما لم يطلب المبرمج تضمين البرامج الفرعية، بطبيعة الحال). وكنتيجة لذلك:
* لا توجد احتمالية للتضخم البرمجي (وهو شائع في ++C ويحتاج لعناية خاصة). * من الممكن استنساخ القوالب أثناء وقت التشغيل وأثناء وقت الترجمة، بما أن النسخ الجديدة لا تحتاج إلى شيفرة كائنية جديدة. * الكائنات الفعلية المقابلة لكائن شكلي قالبي تُعتبر دائماً غير ساكنة داخل القالب؛ انظر Generic formal objects في ويكي الكتب لمزيد من التفاصيل والنتائج.
- بما أن جميع النسخ من القالب متطابقة تماماً، يكون من الأسهل مراجعة البرامج وفهمها من قِبل الآخرين؛ ولا توجد "حالات خاصة" يجب أخذها بالحسبان.
- ولأن جميع الاستنساخات صريحة، فلا توجد استنساخات مخفية قد تجعل من الصعب فهم البرنامج.
- لا تسمح أيدا بإجراء حسابات عشوائية أثناء وقت الترجمة، لأن العمليات على معطيات القوالب تُجرى أثناء وقت التشغيل.
قوالب سي++
[عدل]تستخدم لغة C++ القوالب (بالإنجليزية: Templates) لتمكين تقنيات البرمجة العامة. تتضمن مكتبة C++ المعيارية مكتبة القوالب المعيارية أو STL التي توفر إطار عمل من القوالب لهياكل البيانات والخوارزميات الشائعة. يمكن أيضًا استخدام القوالب في سي++ لبرمجة القوالب الوصفية، وهي وسيلة لتقييم بعض أجزاء الشيفرة في وقت الترجمة بدلاً من زمن التشغيل. باستخدام تخصيص القوالب (بالإنجليزية: Template Specialization)، فإن قوالب سي++ كمال تورنغ.
نظرة فنية عامة
[عدل]توجد أنواع عديدة من القوالب، وأكثرها شيوعًا هي قوالب الدوال وقوالب الأصناف. قالب الدالة هو نمط لإنشاء دوال عادية استنادًا إلى أنواع المعاملات التي تُمرَّر عند الاستدعاء. على سبيل المثال، تحتوي مكتبة القوالب المعيارية على قالب دالة max(x, y)
ينشئ دالة تُرجع إما "x" أو "y" أيهما أكبر. يمكن تعريف max()
كما يلي:
template<typename T>
T max(T x, T y) {
return x < y ? y : x;
}
يمكن استدعاء التخصيصات لهذه الدالة، أي النُسخ المنشأة بأنواع معينة، كما لو كانت دوال عادية:
std::cout << max(3, 7); // تطبع 7
يقوم المترجم بتحليل المعاملات المستخدمة لاستدعاء max
ويحدد أن الاستدعاء هو max(int, int)
. ثم يُنشئ نسخة من الدالة حيث النوع T
هو int
، ويكافئ ذلك الدالة التالية:
int max(int x, int y) {
return x < y ? y : x;
}
يعمل هذا سواء كانت القيم x وy أعدادًا صحيحة أو سلاسل نصية أو أي نوع آخر يمكن تطبيق التعبير x < y
عليه، أو بشكل أدق، لأي نوع تم تعريف العامل operator<
له. لا يشترط وجود وراثة مشتركة بين الأنواع المستخدمة، لذا فهو مشابه لمفهوم تنويع البط. يمكن لأي برنامج يعرّف نوع بيانات مخصص أن يحدد معنى العامل <
لهذا النوع باستخدام تحميل زائد للعملية، مما يسمح باستخدامه مع قالب الدالة max()
. ورغم أن هذه الفائدة قد تبدو بسيطة في هذا المثال المحدود، إلا أنها توفر الكثير من الوظائف في سياق مكتبة شاملة مثل STL؛ إذ يكفي تعريف بعض المعاملات لنوع جديد من البيانات كي يصبح قابلًا للاستخدام مع خوارزميات قياسية مثل sort()
وstable_sort()
وbinary_search()
، أو إدراجه ضمن هياكل بيانات مثل set
وإدارة الذاكرة ومصفوفة ارتباطية.
تُعد قوالب C++ آمنة تمامًا من حيث سلامة الأنماط أثناء وقت الترجمة. كمثال توضيحي، فإن النوع القياسي complex
لا يعرّف العامل <
، لأنه لا توجد علاقة ترتيب صارمة على الأعداد المركبة. لذلك، فإن استدعاء max(x, y)
سيفشل بخطأ في الترجمة إذا كانت x وy من نوع complex
. وبالمثل، لا يمكن تطبيق قوالب أخرى تعتمد على <
على بيانات complex
ما لم يتم توفير دالة مقارنة (على شكل دالة أو كائن دالة). على سبيل المثال: لا يمكن استخدام complex
كمفتاح في map
ما لم تُوفر مقارنة. للأسف، غالبًا ما تُصدر المترجمات رسائل خطأ غامضة وغير مفيدة لهذا النوع من الأخطاء. ويمكن التخفيف من هذه المشكلة بضمان التزام الكائن ببروتوكول معين بروتوكول (اتصالات). كما أن اللغات التي تستخدم دالة compare
بدلاً من العامل <
يمكنها استخدام قيم complex
كمفاتيح.
نوع آخر من القوالب هو "قالب الصنف" (Class Template)، والذي يمد نفس المفهوم ليشمل الأصناف. يُعد التخصيص (Specialization) لقالب صنف بمثابة صنف فعلي. وغالبًا ما تُستخدم قوالب الأصناف لإنشاء حاويات عامة. على سبيل المثال، توفر مكتبة STL قائمة متصلة. لإنشاء قائمة مرتبطة من أعداد صحيحة نكتب list<int>
، ولإنشاء قائمة من سلاسل نصية نكتب list<string>
. تحتوي list
على مجموعة من الدوال القياسية التي تعمل مع أي نوع معامل مناسب.
تخصيص القوالب
[عدل]تُعد "تخصيص القوالب" (بالإنجليزية: Template Specialization) من الميزات القوية في قوالب C++. تتيح هذه الميزة توفير تنفيذات بديلة اعتمادًا على خصائص معينة لنوع المعامل الذي يتم إنشاؤه (instantiated). يهدف تخصيص القوالب إلى أمرين: السماح ببعض أشكال التحسين (Optimization)، وتقليل تضخم الشيفرة (Code Bloat).
على سبيل المثال، لنفترض وجود دالة قالب sort()
. من الأنشطة الأساسية التي تقوم بها هذه الدالة هو تبديل القيم أو استبدالها بين موقعين في الحاوية. إذا كانت القيم كبيرة (من حيث عدد البايتات اللازمة لتخزين كل منها)، فعادةً ما يكون من الأسرع بناء قائمة منفصلة من المؤشرات إلى الكائنات، ثم فرز تلك المؤشرات، وأخيرًا بناء التسلسل النهائي المرتب. أما إذا كانت القيم صغيرة جدًا، فغالبًا ما يكون من الأسرع مجرد تبديل القيم في أماكنها مباشرةً عند الحاجة. علاوة على ذلك، إذا كان النوع المعامل هو بالفعل نوع مؤشّر، فلا حاجة لبناء مصفوفة مؤشرات منفصلة. يتيح تخصيص القوالب لمنشئ القالب كتابة تنفيذات مختلفة وتحديد الخصائص التي يجب أن تتوفر في النوع المعامل ليُستخدم كل تنفيذ.
على عكس قوالب الدوال، يمكن لقوالب الأصناف أن تكون مخصصة جزئيًا. هذا يعني أنه يمكن توفير نسخة بديلة من كود قالب الصنف عندما تكون بعض معلمات القالب معروفة، مع ترك معلمات أخرى عامة. يمكن استخدام ذلك، على سبيل المثال، لإنشاء تنفيذ افتراضي (ويسمى "التخصيص الرئيسي") يفترض أن نسخ النوع المعامل مكلف، ثم إنشاء تخصيصات جزئية لأنواع يسهل نسخها، مما يزيد الكفاءة العامة. يمكن لمستخدمي هذا النوع من قوالب الأصناف استخدام التخصيصات دون الحاجة إلى معرفة ما إذا كان المترجم قد استخدم التخصيص الرئيسي أو تخصيصًا جزئيًا في كل حالة. كما يمكن لقوالب الأصناف أن تكون "مخصصة كليًا"، أي أنه يمكن توفير تنفيذ بديل عندما تكون جميع الأنواع المعلمة معروفة.
المزايا والعيوب
[عدل]بعض استخدامات القوالب، مثل دالة max()
، كانت تُنفّذ سابقًا باستخدام معالج مسبق وماكرو (علم الحاسوب) شبيه بالدالة (وهو من بقايا لغة سي). على سبيل المثال، إليك تنفيذ ممكن لماكرو مماثل:
#define max(a,b) ((a) < (b) ? (b) : (a))
يتم توسيع الماكرو (نسخه ولصقه) من قبل معالج مسبق قبل عملية الترجمة البرمجية، بينما القوالب عبارة عن دوال حقيقية فعلية. يتم دائمًا توسيع الماكرو داخل الكود، بينما يمكن أيضًا للقوالب أن تُستخدم كدالة مضمنة إذا رأى المترجم أن ذلك مناسب.
مع ذلك، تُعتبر القوالب عمومًا تحسينًا على الماكرو لأغراض كهذه. فالقوالب آمنة من حيث النوع، كما أنها تتجنب بعض الأخطاء الشائعة في الشيفرات التي تعتمد بشكل كبير على الماكرو، مثل تقييم المعاملات ذات التأثيرات الجانبية مرتين. وربما الأهم من ذلك، أن القوالب صُممت لتكون قابلة للتطبيق على مشكلات أكبر بكثير من تلك التي تُعالج بالماكرو.
لكن، هناك أربع سلبيات رئيسية لاستخدام القوالب:
- تفتقر القوالب في C++ إلى العديد من الميزات، مما يجعل تنفيذها واستخدامها بطريقة مباشرة أمرًا مستحيلًا في كثير من الأحيان. وبدلًا من ذلك، يضطر المبرمجون للاعتماد على حيل معقدة تؤدي إلى شيفرة منتفخة وصعبة الفهم والصيانة. وتسهم التطورات الحالية في معايير C++ في تفاقم هذه المشكلة، لأنها تستخدم هذه الحيل بشكل كبير وتبني عليها العديد من الميزات الجديدة.
- لطالما كان دعم القوالب ضعيفًا في العديد من المترجمات (compilers)، مما قد يجعل الشيفرة أقل قابلية للنقل بين الأنظمة. كما قد يكون الدعم ضعيفًا عندما يُستخدم مترجم C++ مع رابط لا يدعم C++، أو عند محاولة استخدام القوالب عبر حدود مكتبة برمجية.
- قد تُنتج المترجمات رسائل خطأ طويلة ومربكة وغير مفيدة أحيانًا عند اكتشاف أخطاء في الشيفرات التي تستخدم مبدأ "الفشل في الاستبدال ليس خطأً" (SFINAE)، لا سيما في الإصدارات السابقة لـ C++20.[19]، مما يصعب تطوير القوالب.
- أخيرًا، يتطلب استخدام القوالب من المترجم إنشاء "نسخة" منفصلة من الصنف أو الدالة القالب لكل مجموعة من أنواع المعاملات المستخدمة. (وهذا ضروري لأن الأنواع في C++ ليست بنفس الحجم، وأحجام الحقول تُعدّ مهمة في كيفية عمل الأصناف). لذلك، فإن الاستخدام العشوائي للقوالب يمكن أن يؤدي إلى تضخم الشيفرة (بالإنجليزية: code bloat)، مما يؤدي إلى تنفيذ ملفات تنفيذية ضخمة جدًا. مع ذلك، فإن الاستخدام الحكيم لتخصيص القوالب والتوريث يمكن أن يقلل من هذا التضخم في بعض الحالات:
فهل يمكن استخدام التوريث لتقليل مشكلة تكرار الشيفرة الناتجة عن استخدام القوالب؟ يتضمن ذلك اشتقاق قالب من صنف عادي. وقد أثبتت هذه التقنية نجاحها في الحد من تضخم الشيفرة في الاستخدام الفعلي. أولئك الذين لم يستخدموا مثل هذه التقنية وجدوا أن الشيفرة المكررة قد تستهلك ميغابايتات من المساحة حتى في البرامج متوسطة الحجم.
— بيارن ستروستروب، The Design and Evolution of C++, 1994[20]
5. قد تتطلب الأصناف أو الدوال القالبية وجود "تخصيص صريح" (بالإنجليزية: explicit specialization) لصنف القالب، مما يستلزم إعادة كتابة الصنف بالكامل من أجل معاملات معينة مستخدمة معه.
وقد تسبب التكرارات الإضافية التي تُنتجها القوالب صعوبات لبعض أدوات التنقيح في التعامل معها. فعلى سبيل المثال، عند محاولة تعيين نقطة توقف داخل قالب من ملف المصدر، قد لا تُضبط النقطة في التخصيص الفعلي المطلوب، أو قد تُضبط في كل المواضع التي يتم فيها استخدام هذا القالب.
كذلك، يجب أن يكون كود تنفيذ القالب متاحًا بالكامل (مثلًا، مضمنًا في ملف رأس) لوحدة الترجمة (ملف المصدر) التي تستخدمه. فالقوالب، بما في ذلك جزء كبير من المكتبة القياسية، لا يمكن ترجمتها إذا لم تكن مضمنة في ملفات رأس. (ويُعد هذا على النقيض من الشيفرات غير القالبية، التي يمكن ترجمتها إلى ثنائيات مع توفير ملف رأس يحتوي على التصريحات فقط). وقد يُعدّ هذا عيبًا لأنه يكشف كود التنفيذ، مما يزيل بعض التجريدات، وقد يحد من استخدامه في المشاريع مغلقة المصدر.
القوالب في لغة دي
[عدل]يدعم لغة دي القوالب التي تعتمد في تصميمها على سي++. معظم أساليب قوالب سي++ تعمل في دي دون تعديل، ولكن دي تضيف بعض الوظائف:
- معلمات القوالب في دي لا تقتصر فقط على الأنواع والقيم البدائية (كما كان الحال في C++ قبل C++20)، بل تسمح أيضًا بالقيم الزمنية التي تُحسب أثناء الترجمة (مثل السلاسل النصية والتمثيلات الهيكلية)، وأسماء مستعارة (aliases) لمعرفات عشوائية، بما في ذلك القوالب الأخرى أو التخصيصات القالبية.
- توفر قيود القوالب وعبارة
static if
بديلاً عن سي++ وif constexpr
في C++. - تعبير
is(...)
يسمح بإنشاء قوالب استكشافية للتحقق من سمات الكائن أثناء الترجمة. - يسمح كل من الكلمة المفتاحية
auto
والتعبيرtypeof
باستنتاج النوع للمتغيرات وقيم إرجاع الدوال، مما يسمح بوجود "أنواع فولدمورت" (أنواع لا تحمل اسمًا عالميًا).[21]
تستخدم القوالب في دي بناء جملة مختلفًا عن C++: بينما يتم تضمين معلمات القوالب في C++ داخل أقواس زاوية (مثل Template<param1, param2>
)، يستخدم دي علامة تعجب والأقواس: Template!(param1, param2)
.
هذا يتجنب قالب (سي++) بسبب التضارب مع عوامل المقارنة.
إذا كان هناك معلمة واحدة فقط، يمكن الاستغناء عن الأقواس.
تقليديًا، يجمع دي بين الميزات المذكورة أعلاه لتوفير وقت التصريف باستخدام البرمجة العامة المعتمدة على السمات.
على سبيل المثال، يتم تعريف النطاق كأي نوع يفي بالتحقق الذي يتم بواسطة isInputRange
، والذي يتم تعريفه كما يلي:
template isInputRange(R)
{
enum bool isInputRange = is(typeof(
(inout int = 0)
{
R r = R.init; // يمكن تعريف كائن نطاق
if (r.empty) {} // يمكن اختبار إذا كان فارغًا
r.popFront(); // يمكن استدعاء popFront()
auto h = r.front; // يمكن الحصول على مقدمة النطاق
}));
}
دالة تقبل فقط النطاقات المدخلات يمكنها بعد ذلك استخدام القالب أعلاه في قيد القالب:
auto fun(Range)(Range range)
if (isInputRange!Range)
{
// ...
}
توليد الشيفرة
[عدل]بالإضافة إلى البرمجة الميتاقالبية (بالإنجليزية: template metaprogramming)، تقدم دي عدة ميزات لتمكين توليد الشيفرة أثناء الترجمة:
- يسمح تعبير
import
بقراءة ملف من القرص واستخدام محتوياته كتعبير نصي. - يتيح الانعكاس أثناء الترجمة تفحص القيم والتصريحات وأعضائها أثناء الترجمة.
- تسمح السمات التي يعرفها المستخدمون للمستخدمين بإرفاق معرفات عشوائية للتصريحات، التي يمكن فحصها باستخدام الانعكاس أثناء الترجمة.
- يتيح تنفيذ الدوال أثناء الترجمة (CTFE) جزءًا من دي (مقتصرًا على العمليات الآمنة) ليتم تفسيره أثناء الترجمة.
- تسمح توسيع السلاسل النصية بتقييم وتجميع محتويات تعبير نصي ككود دي الذي يصبح جزءًا من البرنامج.
الجمع بين الميزات السابقة يسمح بتوليد الشيفرة بناءً على التصريحات الموجودة. على سبيل المثال، يمكن لإطارات تسلسل دي فحص أعضاء النوع وتوليد دوال مخصصة لكل نوع مسلسلة من أجل تنفيذ التسلسل وإعادة التسلسل. يمكن أن تشير السمات التي يعرفها المستخدم أيضًا إلى قواعد التسلسل.
يسمح تعبير import
وتنفيذ الدوال أثناء الترجمة أيضًا بتنفيذ لغات مخصصة النطاق بشكل فعال.
على سبيل المثال، نظرًا لدالة تأخذ سلسلة تحتوي على قالب HTML وتُرجع كود دي المعادل، يمكن استخدامها على النحو التالي:
// استيراد محتويات example.htt كقيمة ثابتة نصية.
enum htmlTemplate = import("example.htt");
// تحويل قالب HTML إلى كود D.
enum htmlDCode = htmlTemplateToD(htmlTemplate);
// لصق محتويات htmlDCode ككود D.
mixin(htmlDCode);
العمومية في إيفل
[عدل]تعد القوالب العامة جزءًا من لغة إيفل منذ التصميم الأولي للطريقة واللغة. استخدمت المنشورات الأساسية لإيفل،[22][23] مصطلح العمومية لوصف إنشاء واستخدام القوالب العامة.
العمومية الأساسية غير المقيدة
[عدل]يتم الإعلان عن القوالب العامة باستخدام اسم الفئة وقائمة من "المعلمات العامة الرسمية". في الشيفرة التالية، تحتوي الفئة LIST
على معلمة عامة رسمية واحدة G
class
LIST [G]
...
feature -- Access
item: G
-- العنصر الذي يشير إليه المؤشر حاليًا
...
feature -- تغيير العنصر
put (new_item: G)
-- إضافة `new_item` إلى نهاية القائمة
...
المعلمات العامة الرسمية هي أماكن حجز لأسماء فئات عشوائية سيتم توفيرها عند إعلان القالب العام، كما هو موضح في اشتقاقي القوالب التاليين، حيث تكون ACCOUNT
وDEPOSIT
هما أسماء الفئات الأخرى. تُعتبر ACCOUNT
وDEPOSIT
"المعلمات العامة الفعلية" حيث توفر أسماء فئات حقيقية لتحل محل G
في الاستخدام الفعلي.
list_of_accounts: LIST [ACCOUNT]
-- قائمة الحسابات
list_of_deposits: LIST [DEPOSIT]
-- قائمة الودائع
ضمن نظام النوع في إيفل، على الرغم من أن الفئة LIST [G]
تعتبر فئة، إلا أنها لا تُعتبر نوعًا. ومع ذلك، يتم اعتبار اشتقاق قالب LIST [G]
مثل LIST [ACCOUNT]
نوعًا.
العمومية المقيدة
[عدل]بالنسبة للفئة القائمة التي تم عرضها أعلاه، يمكن أن تكون المعلمة العامة الفعلية التي تحل محل G
أي فئة أخرى متاحة. لتقييد مجموعة الفئات التي يمكن اختيار معلمات عامة فعلية صالحة منها، يمكن تحديد "قيد عام". في إعلان الفئة SORTED_LIST
أدناه، يحدد القيد العام أن أي معلمة عامة فعلية صالحة ستكون فئة ترث من الفئة COMPARABLE
. يضمن القيد العام أن عناصر SORTED_LIST
يمكن فرزها بالفعل.
class
SORTED_LIST [G -> COMPARABLE]
القوالب العامة في جافا
[عدل]تم إضافة دعم للقوالب العامة، أو "الحاويات من النوع T"، إلى جافا في عام 2004 كجزء من J2SE 5.0. في جافا، يتم التحقق من القوالب العامة فقط في وقت الترجمة للتأكد من صحة الأنواع. ثم يتم إزالة معلومات النوع عبر عملية تُسمى محو النوع، للحفاظ على التوافق مع الإصدارات القديمة من آلة جافا الافتراضية، مما يجعلها غير متاحة في وقت التشغيل.[24] على سبيل المثال، يتم تحويل List<String>
إلى النوع الخام List
. يقوم المترجم بإدراج التحويل في c++ لتحويل العناصر إلى نوع String
عندما يتم استرجاعها من القائمة، مما يقلل من الأداء مقارنة بالتنفيذات الأخرى مثل قوالب C++.
القوالب العامة في .NET [C#, VB.NET]
[عدل]تمت إضافة دعم للقوالب العامة كجزء من دوت نت فريموورك في نوفمبر 2005، بناءً على نموذج بحثي من مايكروسوفت بدأ في عام 1999.[25] على الرغم من تشابهها مع القوالب العامة في جافا، إلا أن قوالب .NET لا تستخدم محو النوع،[26]:208–209 ولكنها تنفذ القوالب كآلية من الدرجة الأولى في وقت التشغيل باستخدام إعادة التعيين. يوفر هذا الاختيار في التصميم وظيفة إضافية، مثل السماح ببرمجة انعكاسية مع الحفاظ على أنواع القوالب، والتخفيف من بعض حدود المحو (مثل عدم القدرة على إنشاء مصفوفات قوالب).[27][28] هذا يعني أيضًا أنه لا يوجد تأثير في الأداء من التحويل في c++ في وقت التشغيل أو من عمليات التعبئة التي تكون عادة باهظة التكلفة. عندما يتم استخدام الأنواع الأولية والقيمية كوسائط قوالب، فإنها تحصل على تطبيقات مخصصة، مما يسمح بتنفيذ قوالب حاويات وأساليب فعالة. كما هو الحال في C++ وJava، فإن الأنواع القوالب المتداخلة مثل Dictionary<string, List<int>> هي أنواع صالحة، ومع ذلك يُنصح بتجنبها في توقيعات الأعضاء وفقًا لقواعد تحليل التصميم للرمز.[29]
يسمح .NET بستة أنواع من القيود على أنواع القوالب باستخدام الكلمة المفتاحية where
، بما في ذلك تقييد القوالب لتكون من أنواع القيم، أو أن تكون من فئة معينة، أو أن يكون لها مُنشئ، أو أن تنفذ واجهات.[30] فيما يلي مثال مع قيد الواجهة:
using System;
class Sample
{
static void Main()
{
int[] array = { 0, 1, 2, 3 };
MakeAtLeast<int>(array, 2); // تغيير المصفوفة إلى { 2, 2, 2, 3 }
foreach (int i in array)
Console.WriteLine(i); // طباعة النتائج.
Console.ReadKey(true);
}
static void MakeAtLeast<T>(T[] list, T lowest) where T : IComparable<T>
{
for (int i = 0; i < list.Length; i++)
if (list[i].CompareTo(lowest) < 0)
list[i] = lowest;
}
}
تسمح طريقة MakeAtLeast()
بالتعامل مع المصفوفات، مع عناصر من النوع العام T
. يحدد قيد النوع في الطريقة أن الطريقة قابلة للتطبيق على أي نوع T
ينفذ واجهة IComparable<T>
العامة. يضمن هذا حدوث خطأ في وقت التصريف إذا تم استدعاء الطريقة إذا كان النوع لا يدعم المقارنة. توفر الواجهة الطريقة العامة CompareTo(T)
.
يمكن أيضًا كتابة الطريقة أعلاه بدون القوالب العامة، باستخدام ببساطة النوع غير العام Array
. ومع ذلك، بما أن المصفوفات هي لاتباين وتباين مرافق وتباين معاكس، فإن التحويل لن يكون سلامة الأنماط، ولن يتمكن المترجم من العثور على بعض الأخطاء المحتملة التي سيتم اكتشافها عند استخدام القوالب العامة. بالإضافة إلى ذلك، ستحتاج الطريقة إلى الوصول إلى عناصر المصفوفة كـ object
بدلاً من ذلك، وستتطلب التحويل في c++ لمقارنة عنصرين. (بالنسبة لأنواع القيم مثل int
، يتطلب هذا التعبئة، على الرغم من أنه يمكن التغلب على ذلك باستخدام فئة Comparer<T>
، كما هو الحال في فئات التجميع القياسية).
سلوك ملحوظ للأعضاء الثابتين في فئة .NET العامة هو التخصيص الثابت للأعضاء لكل نوع في وقت التشغيل (انظر المثال أدناه).
// فئة عامة
public class GenTest<T>
{
// متغير ثابت - سيتم إنشاؤه لكل نوع عند الانعكاس
static CountedInstances OnePerType = new CountedInstances();
// عضو بيانات
private T _t;
// مُنشئ بسيط
public GenTest(T t)
{
_t = t;
}
}
// فئة
public class CountedInstances
{
// متغير ثابت - سيتم زيادته مرة واحدة لكل مثيل
public static int Counter;
// مُنشئ بسيط
public CountedInstances()
{
// زيادة العداد بمقدار واحد عند إنشاء الكائن
CountedInstances.Counter++;
}
}
// نقطة الدخول للرمز الرئيسي
// في نهاية التنفيذ، سيكون CountedInstances.Counter = 2
GenTest<int> g1 = new GenTest<int>(1);
GenTest<int> g11 = new GenTest<int>(11);
GenTest<int> g111 = new GenTest<int>(111);
GenTest<double> g2 = new GenTest<double>(1.0);
القوالب العامة في باسكال
[عدل]بالنسبة لباسكال، تم تنفيذ القوالب العامة لأول مرة في عام 2006 في تنفيذ فري باسكال.
في دلفي
[عدل]حصلت أوبجكت باسكال (دلفي) على القوالب العامة في إصدار دلفي 11 عام 2007 بواسطة كود جير، في البداية مع المترجم .NET (الذي تم إيقافه لاحقًا) قبل أن يتم إضافتها إلى الشيفرة الأصلية في إصدار دلفي 12 عام 2009. تمت محاكاة دلالات وقدرات قوالب دلفي إلى حد كبير على قوالب .NET 2.0، على الرغم من أن التنفيذ كان مختلفًا بالضرورة. إليك ترجمة شبه مباشرة لأول مثال C# الذي تم عرضه أعلاه:
program Sample;
{$APPTYPE CONSOLE}
uses
Generics.Defaults; // لاستخدام IComparer<>
type
TUtils = class
class procedure MakeAtLeast<T>(Arr: TArray<T>; const Lowest: T;
Comparer: IComparer<T>); overload;
class procedure MakeAtLeast<T>(Arr: TArray<T>; const Lowest: T); overload;
end;
class procedure TUtils.MakeAtLeast<T>(Arr: TArray<T>; const Lowest: T;
Comparer: IComparer<T>);
var
I: Integer;
begin
if Comparer = nil then Comparer := TComparer<T>.Default;
for I := Low(Arr) to High(Arr) do
if Comparer.Compare(Arr[I], Lowest) < 0 then
Arr[I] := Lowest;
end;
class procedure TUtils.MakeAtLeast<T>(Arr: TArray<T>; const Lowest: T);
begin
MakeAtLeast<T>(Arr, Lowest, nil);
end;
var
Ints: TArray<Integer>;
Value: Integer;
begin
Ints := TArray<Integer>.Create(0, 1, 2, 3);
TUtils.MakeAtLeast<Integer>(Ints, 2);
for Value in Ints do
WriteLn(Value);
ReadLn;
end.
كما في C#، يمكن أن تحتوي الأساليب والأنواع الكاملة على معلمات نوع واحدة أو أكثر. في المثال، TArray
هو نوع عام (مُعرف من قبل اللغة) وMakeAtLeast
هو أسلوب عام. القيود المتاحة مشابهة جدًا لتلك المتاحة في C#: أي نوع من القيم، أي فئة، فئة أو واجهة محددة، وفئة بها مُنشئ بلا معلمات. القيود المتعددة تعمل كاتحاد إضافي.
في فري باسكال
[عدل]تم تنفيذ القوالب العامة في فري باسكال في عام 2006، قبل دلفي وبصيغة ودلالات مختلفة. ومع ذلك، منذ إصدار FPC 2.6.0، أصبحت صيغة دلفي متاحة عند استخدام وضع اللغة {$mode Delphi}
. وبالتالي، يدعم فري باسكال القوالب العامة في أي من الأساليب.
مثال دلفي وفري باسكال:
// أسلوب دلفي
unit A;
{$ifdef fpc}
{$mode delphi}
{$endif}
interface
type
TGenericClass<T> = class
function Foo(const AValue: T): T;
end;
implementation
function TGenericClass<T>.Foo(const AValue: T): T;
begin
Result := AValue + AValue;
end;
end.
// أسلوب فري باسكال ObjFPC
unit B;
{$ifdef fpc}
{$mode objfpc}
{$endif}
interface
type
generic TGenericClass<T> = class
function Foo(const AValue: T): T;
end;
implementation
function TGenericClass.Foo(const AValue: T): T;
begin
Result := AValue + AValue;
end;
end.
// مثال للاستخدام بأسلوب دلفي
program TestGenDelphi;
{$ifdef fpc}
{$mode delphi}
{$endif}
uses
A,B;
var
GC1: A.TGenericClass<Integer>;
GC2: B.TGenericClass<String>;
begin
GC1 := A.TGenericClass<Integer>.Create;
GC2 := B.TGenericClass<String>.Create;
WriteLn(GC1.Foo(100)); // 200
WriteLn(GC2.Foo('hello')); // hellohello
GC1.Free;
GC2.Free;
end.
// مثال للاستخدام بأسلوب ObjFPC
program TestGenDelphi;
{$ifdef fpc}
{$mode objfpc}
{$endif}
uses
A,B;
// مطلوب في ObjFPC
type
TAGenericClassInt = specialize A.TGenericClass<Integer>;
TBGenericClassString = specialize B.TGenericClass<String>;
var
GC1: TAGenericClassInt;
GC2: TBGenericClassString;
begin
GC1 := TAGenericClassInt.Create;
GC2 := TBGenericClassString.Create;
WriteLn(GC1.Foo(100)); // 200
WriteLn(GC2.Foo('hello')); // hellohello
GC1.Free;
GC2.Free;
end.
اللغات الدالية
[عدل]القوالب العامة في هاسكل
[عدل]آلية فئة النوع في هاسكل تدعم البرمجة العامة. ست من فئات النوع المحددة مسبقًا في هاسكل (بما في ذلك Eq
، الأنواع التي يمكن مقارنتها للتساوي، وShow
، الأنواع التي يمكن تمثيل قيمها كسلاسل نصية) تتمتع بخصائص خاصة تدعم "المثيلات المشتقة". هذا يعني أن المبرمج الذي يعرف نوعًا جديدًا يمكنه تحديد أن هذا النوع سيكون مثيلاً لإحدى فئات النوع الخاصة هذه، دون الحاجة إلى تقديم تنفيذات لدوال الفئة كما هو مطلوب عادة عند إعلان مثيلات الفئة. سيتم "استخلاص" جميع الدوال اللازمة – أي، بناؤها تلقائيًا – بناءً على هيكل النوع. على سبيل المثال، الإعلان التالي لنوع من شجرة ثنائية يحدد أنه يجب أن يكون مثيلاً لفئتي Eq
وShow
:
data BinTree a = Leaf a | Node (BinTree a) a (BinTree a)
deriving (Eq, Show)
ينتج عن ذلك تعريف دالة التساوي (==
) ودالة تمثيل السلسلة النصية (show
) تلقائيًا لأي نوع من النوع BinTree T
بشرط أن يدعم T
هذه العمليات.
يدعم استخراج المثيلات لفئتي Eq
وShow
جعل دوالها ==
وshow
عامة بطريقة نوعية مختلفة عن دوال البوليمورفيك البراميترية: يمكن تطبيق هذه "الدوال" (أو بالأحرى، عائلات دوال مُؤشرة على النوع) على قيم من أنواع مختلفة، وعلى الرغم من أنها تتصرف بشكل مختلف لكل نوع من المدخلات، إلا أن القليل من العمل مطلوب لإضافة دعم لنوع جديد. أظهر رالف هينز (2004) أن تأثيرًا مشابهًا يمكن تحقيقه لفئات النوع المعرفة من قبل المستخدم باستخدام تقنيات برمجية معينة. اقترح باحثون آخرون نهجًا لهذا النوع وغيره من أنواع التعميم في سياق هاسكل وتوسعات هاسكل (والتي ستتم مناقشتها لاحقًا).
PolyP
[عدل]كان PolyP هو أول امتداد للبرمجة العامة في هاسكل. في PolyP، تسمى الدوال العامة بـ "بوليتبيك" (Polytypic). يقدم اللغة بناء خاص حيث يمكن تعريف هذه الدوال البوليتبيكية عبر الاستقراء الهيكلي على هيكل الدالة النموذجية لنوع البيانات العادي. أنواع البيانات العادية في PolyP هي مجموعة فرعية من أنواع البيانات في هاسكل. يجب أن يكون النوع العادي t
من النوع * → *، وإذا كان a هو النوع الرسمي في التعريف، فيجب أن تكون جميع الاستدعاءات التكرارية لـ t على شكل t a. هذه القيود تستبعد أنواع البيانات عالية الأنواع وأنواع البيانات المتداخلة، حيث تكون الاستدعاءات التكرارية من شكل مختلف.
دالة flatten
في PolyP هنا موضحة كمثال:
flatten :: Regular d => d a -> [a]
flatten = cata fl
polytypic fl :: f a [a] -> [a]
case f of
g+h -> either fl fl
g*h -> \(x,y) -> fl x ++ fl y
() -> \x -> []
Par -> \x -> [x]
Rec -> \x -> x
d@g -> concat . flatten . pmap fl
Con t -> \x -> []
cata :: Regular d => (FunctorOf d a b -> b) -> d a -> b
هاسكل العامة
[عدل]هاسكل العامة هي امتداد آخر لـ هاسكل، تم تطويره في جامعة أترخت في هولندا. توفر الامتدادات التالية:
- القيم المؤشرة على النوع تُعرف كقيمة مُؤشرة عبر بناء الأنواع في هاسكل (الوحدة، الأنواع البدائية، المجاميع، المنتجات، وبناة الأنواع المُعرفة من قبل المستخدم). بالإضافة إلى ذلك، يمكننا تحديد سلوك القيم المؤشرة على النوع لمُنشئ معين باستخدام "حالات المُنشئ"، وإعادة استخدام تعريف عام في آخر باستخدام "الحالات الافتراضية".
القيمة المؤشرة على النوع الناتجة يمكن تخصيصها لأي نوع.
- أنواع المؤشرات على النوع هي أنواع مؤشرة على الأنواع، تُعرف عبر إعطاء حالة لكل من * وk → k'. يتم الحصول على المثيلات عن طريق تطبيق النوع المؤشر على النوع.
- يمكن استخدام التعريفات العامة عن طريق تطبيقها على نوع أو نوع مؤشر. يُسمى هذا بـ "التطبيق العام". النتيجة هي نوع أو قيمة، حسب نوع التعريف العام الذي يتم تطبيقه.
- التجريد العام يتيح تعريف التعريفات العامة عبر تجريد معلمة النوع (من نوع معين).
- أنواع المؤشرات على النوع هي أنواع مؤشرة على منشئي الأنواع. يمكن استخدامها لإعطاء أنواع لقيم عامة أكثر تعقيدًا. يمكن تخصيص هذه الأنواع المؤشرة على النوع لأي نوع.
كمثال، دالة التساوي في هاسكل العامة:
type Eq {[ * ]} t1 t2 = t1 -> t2 -> Bool
type Eq {[ k -> l ]} t1 t2 = forall u1 u2. Eq {[ k ]} u1 u2 -> Eq {[ l ]} (t1 u1) (t2 u2)
eq {| t :: k |} :: Eq {[ k ]} t t
eq {| Unit |} _ _ = True
eq {| :+: |} eqA eqB (Inl a1) (Inl a2) = eqA a1 a2
eq {| :+: |} eqA eqB (Inr b1) (Inr b2) = eqB b1 b2
eq {| :+: |} eqA eqB _ _ = False
eq {| :*: |} eqA eqB (a1 :*: b1) (a2 :*: b2) = eqA a1 a2 && eqB b1 b2
eq {| Int |} = (==)
eq {| Char |} = (==)
eq {| Bool |} = (==)
كلين
[عدل]تقدم كلين البرمجة العامة عبر PolyP وهاسكل العامة كما يدعمها GHC ≥ 6.0. يتم برمجة القوالب باستخدام النوع، كما هو الحال في هاسكل، ولكنه يوفر التحميل الزائد (بالإنجليزية: Overloading).
لغات أخرى
[عدل]اللغات في عائلة أم أل تدعم البرمجة العامة من خلال التعددية البراميترية والبرمجة التركيبية العامة المسماة "الدوال"، والتي هي مشابهة لقوالب الفئات في C++ وعبوات البرمجة العامة في لغة آدا. توفر كل من أم أل المعيارية ولغة كامل الموضوعية دوال (Functors)، التي تشبه قوالب الفئات في C++ وعبوات البرمجة العامة في آدا. كما أن سكيم توفر تجريدات تركيبية ترتبط بالتعميم - وهي في الواقع مجموعة شاملة من قوالب C++.
قد يتخذ فيريلوج وحدة برمجية واحدة أو أكثر من المعلمات، حيث يتم تعيين قيمها الفعلية عند تنفيذ الوحدة. أحد الأمثلة هو مصفوفة سجل (العتاد) عامة حيث يتم تحديد عرض المصفوفة عبر معلمة. يمكن لهذه المصفوفة، إلى جانب ناقل الأسلاك العام، أن تُشكل ذاكرة أو مخزن عام بعرض بت عشوائي من خلال تنفيذ وحدة واحدة فقط.[31]
يشتمل في إتش دي إل، المشتق من آدا، أيضًا على قدرات عامة.[32]
تدعم سي "التعبيرات العامة المرتبطة بالنوع" باستخدام الكلمة المفتاحية _Generic
[33]
#define cbrt(x) _Generic((x), long double: cbrtl, \
default: cbrt, \
float: cbrtf)(x)
انظر أيضًا
[عدل]المراجع
[عدل]- ^ Lee، Kent D. (15 ديسمبر 2008). Programming Languages: An Active Learning Approach. Springer Science & Business Media. ص. 9–10. ISBN:978-0-387-79422-8. مؤرشف من الأصل في 2024-06-16.
- ^ Milner، R.؛ Morris، L.؛ Newey، M. (1975). "A Logic for Computable Functions with Reflexive and Polymorphic Types". Proceedings of the Conference on Proving and Improving Programs.
- ^ Gamma، Erich؛ Helm، Richard؛ Johnson، Ralph؛ Vlissides، John (1994). Design Patterns. Addison-Wesley. ISBN:0-201-63361-2.
- ^ Musser & Stepanov 1989.
- ^ Musser, David R.؛ Stepanov, Alexander A. Generic Programming (PDF). مؤرشف من الأصل (PDF) في 2025-01-27.
- ^ Alexander Stepanov؛ Paul McJones (19 يونيو 2009). Elements of Programming. Addison-Wesley Professional. ISBN:978-0-321-63537-2.
- ^ Musser, David R.؛ Stepanov, Alexander A. (1987). "A library of generic algorithms in Ada". Proceedings of the 1987 annual ACM SIGAda international conference on Ada - SIGAda '87. ص. 216–225. CiteSeerX:10.1.1.588.7431. DOI:10.1145/317500.317529. ISBN:0897912438. S2CID:795406.
- ^ Alexander Stepanov and Meng Lee: The Standard Template Library. HP Laboratories Technical Report 95-11(R.1), 14 November 1995
- ^ Matthew H. Austern: Generic programming and the STL: using and extending the C++ Standard Template Library. Addison-Wesley Longman Publishing Co., Inc. Boston, MA, USA 1998
- ^ Jeremy G. Siek, Lie-Quan Lee, Andrew Lumsdaine: The Boost Graph Library: User Guide and Reference Manual. Addison-Wesley 2001
- ^ Stepanov، Alexander. Short History of STL (PDF). مؤرشف من الأصل (PDF) في 2025-02-21.
- ^ ا ب Stroustrup، Bjarne. Evolving a language in and for the real world: C++ 1991-2006 (PDF). DOI:10.1145/1238844.1238848. S2CID:7518369. مؤرشف من الأصل (PDF) في 2025-02-11.
- ^ Lo Russo، Graziano. "An Interview with A. Stepanov".
- ^ Backhouse، Roland؛ Jansson، Patrik؛ Jeuring، Johan؛ Meertens، Lambert (1999). Generic Programming – an Introduction (PDF). مؤرشف من الأصل (PDF) في 2024-07-08.
- ^ Lämmel، Ralf؛ Peyton Jones، Simon (يناير 2003). "Scrap Your Boilerplate: A Practical Design Pattern for Generic Programming" (PDF). Microsoft. اطلع عليه بتاريخ 2016-10-16.
- ^ Dos Reis، Gabriel؛ Ja ̈rvi، Jaakko (2005). "What is Generic Programming? (preprint LCSD'05)" (PDF). مؤرشف من الأصل (PDF) في 2005-12-25.
- ^ قالب:Cite CiteSeerX
- ^ "Generic Units". www.adaic.org. اطلع عليه بتاريخ 2024-04-25.
- ^ Stroustrup, Dos Reis (2003): Concepts - Design choices for template argument checking نسخة محفوظة 2025-01-27 على موقع واي باك مشين.
- ^ Stroustrup، Bjarne (1994). "15.5 Avoiding Code Replication". The Design and Evolution of C++. Reading, Massachusetts: Addison-Wesley. ص. 346–348. Bibcode:1994dec..book.....S. ISBN:978-81-317-1608-3.
- ^ Bright، Walter. "Voldemort Types in دي". Dr. Dobbs. اطلع عليه بتاريخ 2015-06-03.
- ^ Object-Oriented Software Construction, Prentice Hall, 1988, و Object-Oriented Software Construction, second edition, Prentice Hall, 1997.
- ^ Eiffel: The Language, Prentice Hall, 1991.
- ^ Bloch 2018، صفحة 126، §Item 28: Prefer lists to arrays.
- ^ .NET/C# Generics History: Some Photos From Feb 1999 نسخة محفوظة 2022-09-05 على موقع واي باك مشين.
- ^ Albahari 2022.
- ^ C#: Yesterday, Today, and Tomorrow: An Interview with Anders Hejlsberg نسخة محفوظة 2012-10-07 على موقع واي باك مشين.
- ^ Generics in C#, Java, and C++ نسخة محفوظة 2019-03-28 على موقع واي باك مشين.
- ^ Code Analysis CA1006: Do not nest generic types in member signatures نسخة محفوظة 2018-08-22 على موقع واي باك مشين.
- ^ Constraints on Type Parameters (C# Programming Guide) نسخة محفوظة 2008-08-20 على موقع واي باك مشين.
- ^ Verilog by Example, Section The Rest for Reference. Blaine C. Readler, Full Arc Press, 2011. (ردمك 978-0-9834973-0-1)
- ^ https://www.ics.uci.edu/~jmoorkan/vhdlref/generics.html VHDL Reference نسخة محفوظة 2025-02-10 على موقع واي باك مشين.
- ^ WG14 N1516 Committee Draft — October 4, 2010 نسخة محفوظة 2025-02-18 على موقع واي باك مشين.
المصادر
[عدل]- Albahari، Joseph (2022). C# 10 in a Nutshell (ط. First). O'Reilly. ISBN:978-1-098-12195-2.
- Bloch، Joshua (2018). "Effective Java: Programming Language Guide" (ط. third). Addison-Wesley. ISBN:978-0134685991.
- Musser، D. R.؛ Stepanov، A. A. (1989). "Generic programming". في P. Gianni (المحرر). Symbolic and Algebraic Computation: International symposium ISSAC 1988. Lecture Notes in Computer Science. ج. 358. ص. 13–25. DOI:10.1007/3-540-51084-2_2. ISBN:978-3-540-51084-0.
- Stroustrup، Bjarne (2007). "Evolving a language in and for the real world: C++ 1991-2006" (PDF). ACM HOPL 2007. مؤرشف من الأصل (PDF) في 2012-05-11.
- Gamma، Erich؛ Helm، Richard؛ Johnson، Ralph؛ Vlissides، John (1994). Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley. Bibcode:1995dper.book.....G. ISBN:0-201-63361-2.
قراءة إضافية
[عدل]- Gabriel Dos Reis و Jaakko Järvi، ما هي البرمجة العامة؟، LCSD 2005 نسخة محفوظة 28 أغسطس 2019 على موقع واي باك مشين..
- Gibbons، Jeremy (2007). "البرمجة العامة بواسطة الأنواع". في Backhouse، R.؛ Gibbons، J.؛ Hinze، R.؛ Jeuring، J. (المحررون). المدرسة الربيعية حول البرمجة العامة بواسطة الأنواع 2006. ملاحظات المحاضرات في علوم الكمبيوتر. Heidelberg: Springer. ج. 4719. ص. 1–71. CiteSeerX:10.1.1.159.1228.
- Meyer، Bertrand (1986). "البرمجة العامة مقابل الوراثة". إجراءات مؤتمر أنظمة البرمجة الكائنية، اللغات والتطبيقات - OOPSLA '86. ص. 391–405. DOI:10.1145/28697.28738. ISBN:0897912047. S2CID:285030.
الوصلات الخارجية
[عدل]- generic-programming.org
- Alexander A. Stepanov، مجموعة أوراق ألكسندر أ. ستبانوف (مؤسس مكتبة القوالب المعيارية)
- سي++, دي
- Walter Bright، القوالب المعاد النظر فيها.
- David Vandevoorde، Nicolai M Josuttis، قوالب C++: الدليل الكامل، 2003 Addison-Wesley. (ردمك 0-201-73484-2)
- سي#, .نيت
- Jason Clark، "تقديم القوالب في CLR الخاص بشركة Microsoft"، سبتمبر 2003، مجلة MSDN، Microsoft.
- Jason Clark، "المزيد عن القوالب في CLR الخاص بشركة Microsoft"، أكتوبر 2003، مجلة MSDN، Microsoft.
- M. Aamir Maniar، Generics.Net. مكتبة قوالب مفتوحة المصدر لـ C#.
- دلفي، أوبجكت باسكال
- Nick Hodges، "دليل مراجعي Delphi 2009"، أكتوبر 2008، شبكة مطوري Embarcadero، Embarcadero.
- Craig Stuntz، "Delphi 2009 القوالب وقيود النوع"، أكتوبر 2008
- Dr. Bob، "Delphi 2009 القوالب"
- فري باسكال: دليل مرجع Free Pascal الفصل 8: القوالب، Michaël Van Canneyt، 2007
- Delphi for Win32: القوالب مع Delphi 2009 Win32، Sébastien DOERAENE، 2008
- Delphi for .NET: Delphi القوالب، Felix COLIBRI، 2008
- إيفل
- هاسكل
- Johan Jeuring، Sean Leather، José Pedro Magalhães، وAlexey Rodriguez Yakushev. المكتبات للبرمجة العامة في هاسكل. جامعة أوترخت.
- Dæv Clarke، Johan Jeuring وAndres Löh، دليل المستخدم لـ Generic Haskell
- Ralf Hinze، "القوالب من أجل الجميع"، في إجراءات جمعية آلات الحوسبة SIGPLAN المؤتمر الدولي للبرمجة الوظيفية (ICFP)، 2004.
- سيمون بيتن جونز، محرر، تقرير لغة هاسكل 98، المنقح 2002.
- Ralf Lämmel وسيمون بيتن جونز، "تخلص من الرتابة: نمط تصميم عملي للبرمجة العامة"، في إجراءات ورشة العمل الدولية لـ جمعية آلات الحوسبة SIGPLAN حول الأنواع في تصميم وتنفيذ اللغات (TLDI'03)، 2003. (انظر أيضًا الموقع المخصص لهذا البحث)
- Andres Löh، استكشاف Generic Haskell, أطروحة دكتوراه، 2004 جامعة أترخت. (ردمك 90-393-3765-9)
- Generic Haskell: لغة للبرمجة العامة
- جافا
- Gilad Bracha، القوالب في لغة البرمجة جافا, 2004.
- Maurice Naftalin و Philip Wadler، Java Generics and Collections، 2006، O'Reilly Media, Inc. (ردمك 0-596-52775-6)
- Peter Sestoft، Java Precisely, الطبعة الثانية، 2005 MIT Press. (ردمك 0-262-69325-9)
- قالب:Javadoc:SE-guide، 2004 Sun Microsystems, Inc.
- Angelika Langer، أسئلة شائعة حول قوالب جافا