تباين النوع

في برمجة الحاسوب ، يُعرَّف تباين النوع بأنه العلاقة بين الأنواع الفرعية لنوع مُركَّب (مثلاً List[Int]) والأنواع الفرعية لمكوناته (مثلاً Int). ويُحدِّد التباين المُختار في لغة البرمجة العلاقة بين، على سبيل المثال، قائمة من Cats وقائمة من Animals، أو دالة تُرجع قيمةCat ودالة تُرجع قيمة Animal.

إذا كان النوعCat نوعًا فرعيًا من Animal، فيجب أن يكون تعبير النوع قابلاً للاستبدال أينما استُخدم تعبير النوع . اعتمادًا على تباين مُنشئ النوع ، قد تُحفظ علاقة النوع الفرعي للأنواع البسيطة، أو تُعكس، أو تُتجاهل بالنسبة للأنواع المركبة المقابلة. في العديد من لغات البرمجة، على سبيل المثال، ستكون "قائمة من القطط" نوعًا فرعيًا من "قائمة من الحيوانات"، لأن مُنشئ نوع القائمة متغاير . هذا يعني أن علاقة النوع الفرعي للأنواع البسيطة محفوظة للأنواع المركبة. من ناحية أخرى، تُعد "دالة من حيوان إلى سلسلة نصية" نوعًا فرعيًا من "دالة من قطة إلى سلسلة نصية" لأن مُنشئ نوع الدالة غير متغاير في نوع المعامل . هنا، تُعكس علاقة النوع الفرعي للأنواع البسيطة بالنسبة للأنواع المركبة.CatAnimal

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

من أجل الحفاظ على بساطة نظام الأنواع والسماح ببرامج مفيدة، قد تتعامل اللغة مع مُنشئ النوع على أنه ثابت حتى لو كان من الآمن اعتباره متغيرًا، أو تعامله على أنه متغير مشترك على الرغم من أن ذلك قد ينتهك سلامة النوع .

أصل الكلمة

تأتي هذه المصطلحات من مفهوم الدوال المتغيرة والمتغيرة عكسيًا في نظرية الفئات . لنفترض الفئةج{\displaystyle C}التي تكون كائناتها أنواعًا، وتمثل مورفيزماتها علاقة النوع الفرعي ≤. (هذا مثال على كيفية اعتبار أي مجموعة مرتبة جزئيًا فئةً). على سبيل المثال، يأخذ مُنشئ نوع الدالة نوعين p و r ويُنشئ نوعًا جديدًا pr ؛ لذا فهو يأخذ كائنات فيج2{\displaystyle C^{2}}إلى الكائنات فيج{\displaystyle C}. وفقًا لقاعدة التنميط الفرعي لأنواع الدوال، تعكس هذه العملية ≤ للمعامل الأول وتحافظ عليه للمعامل الثاني، لذا فهي دالة متغايرة في المعامل الأول ودالة متغايرة في المعامل الثاني.

التعريف الرسمي

لنفترض أن Aو Bنوعان، و I<U>يشير إلى تطبيق مُنشئ نوعI مع وسيط نوع U. ضمن نظام أنواع لغة برمجة، تكون قاعدة تحديد النوع لمنشئ النوع Iكما يلي:

  • متغير مشترك إذا حافظ على ترتيب الأنواع (≤) ، والذي يرتب الأنواع من الأكثر تحديدًا إلى الأكثر عمومية: إذا كان A ≤ B، فإن ؛I<A> ≤ I<B>
  • متغاير عكسيًا إذا عكس هذا الترتيب: إذا كان A ≤ B، فإن ؛I<B> ≤ I<A>
  • يكون ثنائي المتغير إذا انطبق كلا الشرطين (أي، إذا كان A ≤ B، فإن )؛ [ ملاحظة 1 ]I<A> ≡ I<B>
  • متغير إذا كان متغيرًا مشتركًا أو متغيرًا عكسيًا أو متغيرًا ثنائيًا؛
  • ثابت أو غير متغير إن لم يكن متغيراً.

تتناول المقالة كيفية تطبيق ذلك على بعض مُنشئات الأنواع الشائعة.

أمثلة على لغة C#

على سبيل المثال، في لغة C# ، إذا Catكان نوعاً فرعياً من Animal، فإن:

  • IEnumerable<Cat>هو نوع فرعي من . يتم الحفاظ على التصنيف الفرعي لأن متغير مشترك على .IEnumerable<Animal>IEnumerable<T>T
  • Action<Animal>هو نوع فرعي من . يتم عكس التصنيف الفرعي لأن متغاير عكسيًا على .Action<Cat>Action<T>T
  • لا يُعد أي منهما نوعًا فرعيًا من الآخر، لأن ثابت على .IList<Cat>IList<Animal>IList<T>T

يُعلن عن تباين واجهة عامة في لغة C# بوضع السمة out(covariant) أو in(contravariant) على (صفر أو أكثر من) معلمات النوع الخاصة بها. [ 1 ] : 144 تُعلن الواجهات المذكورة أعلاه على النحو التالي : و و . قد تُحدد الأنواع التي تحتوي على أكثر من معلمة نوع واحدة تباينات مختلفة على كل معلمة نوع. على سبيل المثال، يُمثل نوع المندوب دالة ذات مُدخل مُتغير عكسيًا من النوع وقيمة إرجاع مُتغيرة من النوع . [ 2 ] [ 1 ] : 145 يتحقق المُصرّف من تعريف جميع الأنواع واستخدامها بما يتوافق مع تعليقاتها التوضيحية، وإلا فإنه يُشير إلى خطأ في التجميع.IEnumerable<outT>Action<inT>IList<T>Func<inT,outTResult>TTResult

تضمن قواعد تحديد أنواع البيانات لتنوع الواجهات سلامة النوع. على سبيل المثال، يمثل `an` دالة من الدرجة الأولى تتوقع وسيطًا من النوع `a` ، [ 1 ] : 144، ويمكن دائمًا استخدام دالة يمكنها التعامل مع أي نوع من الحيوانات بدلاً من دالة يمكنها التعامل مع القطط فقط.Action<T>T

المصفوفات

يمكن أن تكون أنواع البيانات للقراءة فقط (المصادر) متغيرة مع بعضها البعض، بينما يمكن أن تكون أنواع البيانات للكتابة فقط (المستقبلات) متغيرة عكسيًا. أما أنواع البيانات القابلة للتغيير التي تعمل كمصادر ومستقبلات في آنٍ واحد، فينبغي أن تكون ثابتة. لتوضيح هذه الظاهرة العامة، لنأخذ نوع المصفوفة . بالنسبة لهذا النوع ، يمكننا إنشاء النوع ، وهو "مصفوفة من الحيوانات". لأغراض هذا المثال، تدعم هذه المصفوفة قراءة وكتابة العناصر.AnimalAnimal[]

لدينا الخيار في التعامل مع هذا الأمر على النحو التالي:

  • المتغير المشترك: أ هو ؛Cat[]Animal[]
  • المتغير المعاكس: an هو a ؛Animal[]Cat[]
  • ثابت: an ليس a و a ليس an .Animal[]Cat[]Cat[]Animal[]

إذا أردنا تجنب أخطاء النوع، فإن الخيار الثالث فقط هو الآمن. من الواضح أنه لا يمكن التعامل مع كل عنصر كما لو كان عنصرًا من نوع ، لأن العميل الذي يقرأ من المصفوفة سيتوقع عنصرًا من نوع ، لكن قد يحتوي عنصر على عنصر من نوع . لذا، فإن قاعدة التباين العكسي غير آمنة.Animal[]Cat[]CatAnimal[]Dog

على النقيض، لا يمكن التعامل مع a كـ . يجب أن يكون من الممكن دائمًا وضع a في . مع المصفوفات المتغيرة، لا يمكن ضمان سلامة ذلك، لأن المخزن الأساسي قد يكون في الواقع مصفوفة من القطط. لذا، فإن قاعدة التغير المشترك ليست آمنة أيضًا - يجب أن يكون مُنشئ المصفوفة ثابتًا . لاحظ أن هذه مشكلة خاصة بالمصفوفات القابلة للتغيير فقط؛ قاعدة التغير المشترك آمنة للمصفوفات غير القابلة للتغيير (للقراءة فقط). وبالمثل، ستكون قاعدة التغير العكسي آمنة للمصفوفات القابلة للكتابة فقط.Cat[]Animal[]DogAnimal[]

المصفوفات المتغيرة في جافا وسي شارب

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

على سبيل المثال، لنفترض كتابة دالة لخلط عناصر مصفوفة، أو دالة تختبر تساوي مصفوفتين باستخدام الدالة ` .`Object على عناصرهما. لا يعتمد تنفيذ هذه الدالة على نوع العنصر المخزن في المصفوفة، لذا يُمكن كتابة دالة واحدة تعمل على جميع أنواع المصفوفات. من السهل تنفيذ دوال من النوع:equals

دالة منطقية equalArrays ( Object [] a1 , Object [] a2 ); دالة void shuffleArray ( Object [] a );

لكن لو تم التعامل مع أنواع المصفوفات كثوابت، لكان من الممكن استدعاء هذه الدوال فقط على مصفوفة من النوع المحدد . على سبيل المثال، لا يمكن خلط مصفوفة من السلاسل النصية.Object[]

لذلك، تتعامل كل من لغتي Java وC# مع أنواع المصفوفات بشكل متغيّر. على سبيل المثال، في Java، يُعدّ نوعًا فرعيًا من ، وفي C#، يُعدّ نوعًا فرعيًا من .String[]Object[]string[]object[]

كما ذُكر سابقًا، تُسبب المصفوفات المتغيرة مشاكل عند الكتابة إليها. تعالج لغتا Java [ 3 ] : 126 وC# هذه المشكلة بتحديد نوع كل عنصر من عناصر المصفوفة عند إنشائه. في كل مرة تُخزَّن قيمة في مصفوفة، يتحقق نظام التشغيل من تطابق نوع القيمة مع نوع المصفوفة. في حال وجود عدم تطابق، يُطلق استثناء (Java) [ 3 ] : 126 أو (C#).ArrayStoreExceptionArrayTypeMismatchException

// a عبارة عن مصفوفة من عنصر واحد من نوع String String [] a = new String [ 1 ] ;// b عبارة عن مصفوفة من الكائنات Object [ ] b = a ;// إسناد قيمة عددية صحيحة (int) إلى b. سيكون هذا ممكنًا لو كانت b في الواقع // مصفوفة من الكائنات، ولكن بما أنها في الواقع مصفوفة من السلاسل النصية، // فسنحصل على استثناء java.lang.ArrayStoreException أثناء التشغيل. b [ 0 ] = 1 ;

في المثال أعلاه، يمكن قراءة البيانات من المصفوفة (ب) بأمان. المشكلة تكمن فقط في محاولة الكتابة إلى المصفوفة.

من عيوب هذا الأسلوب أنه يترك احتمال حدوث خطأ أثناء التشغيل، وهو خطأ كان من الممكن أن يكتشفه نظام أنواع أكثر صرامة أثناء الترجمة. كما أنه يؤثر سلبًا على الأداء لأن كل عملية كتابة في مصفوفة تتطلب فحصًا إضافيًا أثناء التشغيل.

مع إضافة الأنواع العامة، توفر لغتا جافا [ 3 ] : 126-129 وسي شارب الآن طرقًا لكتابة هذا النوع من الدوال متعددة الأشكال دون الاعتماد على التغاير. ويمكن إعطاء دوال مقارنة المصفوفات ودوال خلطها أنواعًا مُعاملة.

<T> دالة منطقية equalArrays ( T [] a1 , T [] a2 ); <T> دالة فارغة shuffleArray ( T [ ] a ) ;

بدلاً من ذلك، ولفرض أن طريقة C# تصل إلى مجموعة بطريقة للقراءة فقط، يمكن للمرء استخدام الواجهة بدلاً من تمرير مصفوفة إليها .IEnumerable<object>object[]

أنواع الدوال

اللغات التي تحتوي على وظائف من الدرجة الأولى لديها أنواع وظائف مثل "وظيفة تتوقع قطة وتعيد حيوانًا" (مكتوبة بصيغة OCaml أو بصيغة C# ).cat->animalFunc<Cat,Animal>

يجب أن تحدد هذه اللغات أيضًا متى يكون نوع دالة ما نوعًا فرعيًا من نوع آخر ، أي متى يكون من الآمن استخدام دالة من نوع معين في سياق يتوقع دالة من نوع مختلف. من الآمن استبدال الدالة f بالدالة g إذا كانت f تقبل وسيطًا من نوع أكثر عمومية وتعيد وسيطًا من نوع أكثر تحديدًا من g . على سبيل المثال، يمكن استخدام الدوال من النوعين a و g و g حيثما كان يُتوقع استخدام a. (يمكن مقارنة هذا بمبدأ متانة الاتصال: "كن متساهلًا فيما تقبله ومتحفظًا فيما تُنتجه"). القاعدة العامة هي:animal->catcat->catanimal->animalcat->animal

(P1R1)(P2R2){\displaystyle (P_{1}\rightarrow R_{1})\leq (P_{2}\rightarrow R_{2})}لوP1P2{\displaystyle P_{1}\geq P_{2}}وR1R2{\displaystyle R_{1}\leq R_{2}}.

باستخدام تدوين قواعد الاستدلال، يمكن كتابة القاعدة نفسها على النحو التالي:

P1P2R1R2(P1R1)(P2R2){\displaystyle {\frac {P_{1}\geq P_{2}\quad R_{1}\leq R_{2}}{(P_{1}\rightarrow R_{1})\leq (P_{2}\rightarrow R_{2})}}}

بمعنى آخر، يكون مُنشئ النوع → متغايرًا عكسيًا في نوع المُعامل (المُدخل) ومتغايرًا مشتركًا في نوع الإرجاع (المُخرج) . وقد صاغ هذه القاعدة رسميًا لأول مرة جون سي. رينولدز ، [ 4 ] ثم شاع استخدامها في ورقة بحثية للوكا كارديلي . [ 5 ]

عند التعامل مع الدوال التي تأخذ دوالًا أخرى كوسائط ، يمكن تطبيق هذه القاعدة عدة مرات. على سبيل المثال، بتطبيق القاعدة مرتين، نرى أن((P1R)R)((P2R)R){\displaystyle ((P_{1}\to R)\to R)\leq ((P_{2}\to R)\to R)}لوP1P2{\displaystyle P_{1}\leq P_{2}}بمعنى آخر، النوع((أب)ج){\displaystyle ((A\to B)\to C)}متغيرة في موضعأ{\displaystyle A}بالنسبة للأنواع المعقدة، قد يكون من المربك تتبع سبب كون تخصص نوع معين آمنًا من حيث النوع أو غير آمن، ولكن من السهل حساب المواضع المتغيرة والمتضادة: يكون الموضع متغيرًا إذا كان على الجانب الأيسر من عدد زوجي من الأسهم التي تنطبق عليه.

الوراثة في لغات البرمجة الكائنية التوجه

في لغات البرمجة كائنية التوجه (OOP)، عندما تُعيد فئة فرعية تعريف دالة في فئة أصلية، يجب على المُصرّف التحقق من أن الدالة المُعاد تعريفها من النوع الصحيح. بينما تشترط بعض اللغات أن يتطابق النوع تمامًا مع النوع في الفئة الأصلية (الثبات)، فإنه من الآمن أيضًا السماح للدالة المُعاد تعريفها بأن يكون لها نوع "أفضل". وفقًا لقاعدة التوريث المعتادة لأنواع الدوال، يعني هذا أن الدالة المُعاد تعريفها يجب أن تُرجع نوعًا أكثر تحديدًا (تباين نوع الإرجاع) وأن تقبل وسيطًا أكثر عمومية (تباين نوع المعامل). في لغة النمذجة الموحدة (UML)، تكون الاحتمالات كما يلي (حيث الفئة B هي الفئة الفرعية التي ترث من الفئة A، وهي الفئة الأصلية):

كمثال عملي، لنفترض أننا نكتب فئة لنمذجة ملجأ للحيوانات . نفترض أن هذه الفئة هي فئة فرعية من الفئة الأساسية، وأن لدينا فئة أساسية (باستخدام صيغة جافا ).CatAnimal

مخطط UML
فئة مأوى الحيوانات {Animal getAnimalForAdoption () { // ... } void putAnimal ( Animal Animal ) { //... } }

والسؤال الآن هو: إذا قمنا بإنشاء فئة فرعية ، فما هي الأنواع التي يُسمح لنا بإعطائها لـ و ؟AnimalSheltergetAnimalForAdoptionputAnimal

نوع إرجاع الطريقة المتغيرة

في لغة تسمح بأنواع الإرجاع المتغيرة ، يمكن للفئة المشتقة تجاوز الطريقة لإرجاع نوع أكثر تحديدًا:getAnimalForAdoption

مخطط UML
class CatShelter extends AnimalShelter {Cat getAnimalForAdoption () { return new Cat (); } }

من بين لغات البرمجة الكائنية الشائعة، تدعم لغات Java و C++ و C# (ابتداءً من الإصدار 9.0 [ 6 ] ) أنواع الإرجاع المتغيرة. وكانت إضافة نوع الإرجاع المتغير أحد التعديلات الأولى على لغة C++ التي أقرتها لجنة المعايير عام 1998. [ 7 ] كما تدعم لغتا Scala و D أنواع الإرجاع المتغيرة.

نوع معلمات طريقة التباين العكسي

وبالمثل، من الآمن من حيث النوع السماح لطريقة التجاوز بقبول وسيط أكثر عمومية من الطريقة الموجودة في الفئة الأساسية:

مخطط UML
class CatShelter extends AnimalShelter { void putAnimal ( Object animal ) { // ... } }

لا تسمح بذلك إلا قلة من لغات البرمجة الكائنية (على سبيل المثال، بايثون عند التحقق من نوعها باستخدام mypy ). أما لغات C++ وجافا ومعظم اللغات الأخرى التي تدعم التحميل الزائد و/أو التظليل ، فستفسر ذلك على أنه دالة ذات اسم مُحمّل أو مُظلل.

مع ذلك، يدعم ساثر كلاً من التغاير والتغاير العكسي. وتكون اصطلاحات استدعاء الطرق المُعاد تعريفها متغايرة مع المعاملات الخارجية وقيم الإرجاع، ومتغايرة عكسياً مع المعاملات العادية (مع النمط في ).

نوع معلمات طريقة التغاير

تسمح لغتا البرمجة الشائعتان، إيفل ودارت [ 8 بأن تكون معاملات الدالة المُعاد تعريفها من نوع أكثر تحديدًا من نوع معاملات الدالة في الفئة الأصلية (تباين نوع المعاملات). وبالتالي، سيُجري كود دارت التالي فحصًا للنوع، مع إعادة تعريف الدالة في الفئة الأصلية:putAnimal

مخطط UML
class CatShelter extends AnimalShelter {void putAnimal ( covariant Cat animal ) { // ... } }

هذا ليس آمنًا من ناحية النوع. فبتحويل نوع a إلى نوع آخر ، يمكن محاولة وضع كلب في ملجأ للقطط. هذا لا يفي بشروط المعاملات وسيؤدي إلى خطأ أثناء التشغيل. يُعدّ نقص أمان النوع (المعروف باسم "مشكلة catcall" في مجتمع Eiffel، حيث "cat" أو "CAT" هو نوع أو حالة متغيرة) مشكلةً مزمنة. على مرّ السنين، اقتُرحت توليفات مختلفة من التحليل الثابت العام، والتحليل الثابت المحلي، وميزات لغوية جديدة لمعالجتها، [ 9 ] [ 10 ] وقد طُبّقت هذه التوليفات في بعض مُجمّعات Eiffel.CatShelterAnimalShelterCatShelter

على الرغم من مشكلة سلامة الأنواع، يعتبر مصممو لغة إيفل أن أنواع المعاملات المتغيرة ضرورية لنمذجة متطلبات العالم الحقيقي. [ 10 ] يُجسد مأوى القطط ظاهرة شائعة: فهو نوع من مآوي الحيوانات ولكنه يخضع لقيود إضافية ، ويبدو من المنطقي استخدام الوراثة وأنواع المعاملات المقيدة لنمذجة ذلك. عند اقتراح هذا الاستخدام للوراثة، يرفض مصممو إيفل مبدأ استبدال ليسكوف ، الذي ينص على أن كائنات الفئات الفرعية يجب أن تكون دائمًا أقل تقييدًا من كائنات فئتها الأصلية.

من الأمثلة الأخرى على اللغات الشائعة التي تسمح بالتغاير في معاملات الدوال، لغة PHP فيما يتعلق بمنشئات الأصناف. في المثال التالي، يتم قبول الدالة `__construct()`، على الرغم من أن معامل الدالة متغاير مع معامل دالة الصنف الأب. لو كانت هذه الدالة أي دالة أخرى غير `__construct()`، لحدث خطأ.

واجهة AnimalInterface {}واجهة DogInterface تمتد من واجهة AnimalInterface {}class Dog implements DogInterface {}class Pet { public function __construct ( AnimalInterface $animal ) {} }class PetDog extends Pet { public function __construct ( DogInterface $dog ) { parent :: __construct ( $dog ); } }

مثال آخر على فائدة المعاملات المتغيرة هو ما يُسمى بالأساليب الثنائية، أي الأساليب التي يُتوقع أن يكون المعامل فيها من نفس نوع الكائن الذي يُستدعى عليه الأسلوب. مثال على ذلك الأسلوب الذي يتحقق مما إذا كان العنصر يأتي قبل أو بعد عنصر آخر في ترتيب معين، ولكن طريقة مقارنة عددين نسبيين، على سبيل المثال، تختلف عن طريقة مقارنة سلسلتين نصيتين. تشمل الأمثلة الشائعة الأخرى للأساليب الثنائية اختبارات المساواة، والعمليات الحسابية، وعمليات المجموعات مثل المجموعات الجزئية والاتحاد.compareToa.compareTo(b)ab

في الإصدارات القديمة من جافا، تم تحديد طريقة المقارنة كواجهة :Comparable

واجهة قابلة للمقارنة {int compareTo ( Object o ); }

يكمن عيب هذا الأسلوب في أنه مُحدد ليأخذ وسيطًا من نوع معين . في التنفيذ النموذجي، يتم أولًا تحويل هذا الوسيط إلى نوع أصغر (مع إظهار خطأ إذا لم يكن من النوع المتوقع).Object

class RationalNumber implements Comparable { int numerator ; int denominator ; // ... public int compareTo ( Object other ) { RationalNumber otherNum = ( RationalNumber ) other ; return Integer . compare ( numerator * otherNum . denominator , otherNum . numerator * denominator ); } }

في لغة ذات معاملات متغيرة، يمكن إعطاء الوسيط للدالة النوع المطلوب مباشرةً ، مما يخفي عملية تحويل النوع. (بالطبع، سيؤدي هذا إلى حدوث خطأ أثناء التشغيل إذا تم استدعاء الدالة على سبيل المثال على متغير من النوع .)compareToRationalNumbercompareToString

تجنب الحاجة إلى أنواع المعلمات المتغيرة

يمكن أن توفر ميزات اللغة الأخرى الفوائد الواضحة للمعاملات المتغيرة مع الحفاظ على قابلية استبدال ليسكوف.

في لغة برمجة تدعم الأنواع العامة (المعروفة أيضًا بتعدد الأشكال البارامتري ) والكمية المحدودة ، يمكن كتابة الأمثلة السابقة بطريقة آمنة من حيث النوع. [ 11 ] بدلًا من تعريف ، نُعرّف فئة مُعاملة . (من عيوب هذا أن مُنفِّذ الفئة الأساسية يحتاج إلى توقع الأنواع التي ستحتاج إلى تخصيصها في الفئات الفرعية).AnimalShelterShelter<T>

class Shelter < T extends Animal > {T getAnimalForAdoption () { // ... }باطلة putAnimal ( T الحيوان ) { // ... } }class CatShelter extends Shelter <Cat> {Cat getAnimalForAdoption () { // ... }باطلة putAnimal ( حيوان القط ) { // ... } }

وبالمثل، في الإصدارات الحديثة من جافا، تم تحديد معلمات للواجهة، مما يسمح بحذف التحويل التنازلي بطريقة آمنة من حيث النوع:Comparable

class RationalNumber implements Comparable <RationalNumber> {int numerator ; int denominator ; // ... public int compareTo ( RationalNumber otherNum ) { return Integer.compare ( numerator * otherNum.denominator , otherNum.numerator * denominator ) ; } }

من الميزات اللغوية الأخرى التي قد تُساعد هي خاصية الإرسال المتعدد . أحد أسباب صعوبة كتابة الدوال الثنائية هو أنه في استدعاء مثل هذا ، يعتمد اختيار التنفيذ الصحيح للدالة على نوع وقت التشغيل لكل من `and` و`and` ، بينما في لغة برمجة كائنية تقليدية، يُؤخذ نوع وقت التشغيل لـ `and` فقط في الاعتبار. في لغة تستخدم الإرسال المتعدد على غرار نظام كائنات Common Lisp (CLOS) ، يُمكن كتابة دالة المقارنة كدالة عامة حيث تُستخدم كلتا الوسيطتين لاختيار الدالة.a.compareTo(b)compareToaba

لاحظ جوزيبي كاستاغنا [ 12 ] أنه في لغة برمجة ذات كتابة متعددة، يمكن أن تحتوي الدالة العامة على بعض المعاملات التي تتحكم في عملية الإرسال، وبعض المعاملات "المتبقية" التي لا تتحكم بها. ولأن قاعدة اختيار الطريقة تختار الطريقة الأكثر تحديدًا وقابلية للتطبيق، فإذا قامت طريقة ما بتجاوز طريقة أخرى، فإن الطريقة المتجاوزة ستحتوي على أنواع أكثر تحديدًا للمعاملات المتحكمة. من ناحية أخرى، ولضمان سلامة النوع، يجب أن تشترط اللغة أن تكون المعاملات المتبقية عامة على الأقل بنفس القدر. باستخدام المصطلحات السابقة، فإن الأنواع المستخدمة لاختيار الطريقة في وقت التشغيل متغيرة، بينما الأنواع غير المستخدمة لاختيار الطريقة في وقت التشغيل متغيرة عكسيًا. وتلتزم لغات البرمجة التقليدية ذات الإرسال الأحادي، مثل جافا، بهذه القاعدة أيضًا: حيث يُستخدم وسيط واحد فقط لاختيار الطريقة (كائن المُستقبِل، الذي يُمرر إلى الطريقة كوسيط مخفي )، وبالفعل يكون نوع هذا الكائن أكثر تخصصًا داخل الطرق المتجاوزة منه في الفئة الأصلية.thisthis

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

ملخص التباين والوراثة

يلخص الجدول التالي قواعد تجاوز الأساليب في اللغات التي تمت مناقشتها أعلاه.

نوع المعاملنوع الإرجاع
C++ (منذ عام 1998)، Java (منذ J2SE 5.0D ، C# (منذ C# 9)ثابتمتغير مشترك
سي شارب (قبل سي شارب 9)ثابتثابت
ساذرالمتناقضمتغير مشترك
إيفلمتغير مشتركمتغير مشترك

الأنواع العامة

في لغات البرمجة التي تدعم الأنواع العامة (المعروفة أيضًا باسم تعدد الأشكال البارامتري )، يستطيع المبرمج توسيع نظام الأنواع باستخدام مُنشئات جديدة. على سبيل المثال، تُمكّن واجهة C# من إنشاء أنواع جديدة مثل `<type>` أو `<type> `. وهنا يبرز السؤال: ما هو التباين المطلوب في مُنشئات هذه الأنواع؟IList<T>IList<Animal>IList<Cat>

هناك منهجان رئيسيان. في اللغات التي تستخدم تعليقات توضيحية لتباين موقع التصريح (مثل C# )، يقوم المبرمج بتعليق تعريف النوع العام بتباين معلمات نوعه المقصود. أما في اللغات التي تستخدم تعليقات توضيحية لتباين موقع الاستخدام (مثل Java )، فيقوم المبرمج بدلاً من ذلك بتعليق المواضع التي يتم فيها إنشاء النوع العام.

تعليقات توضيحية لتباين موقع الإعلان

تُعدّ لغات C# و Kotlin (باستخدام الكلمتين المفتاحيتين ` outvariance` و`variance` in) و Scala و OCaml (باستخدام الكلمتين المفتاحيتين `variance` +و`variance` ) من أكثر اللغات شيوعًا التي تدعم التعليقات التوضيحية للتباين في موقع الإعلان. -تسمح لغة C# فقط بالتعليقات التوضيحية للتباين لأنواع الواجهات، بينما تسمح لغات Kotlin وScala وOCaml بها لكل من أنواع الواجهات وأنواع البيانات الملموسة.

واجهات

في لغة C#، يمكن تمييز كل مُعامل نوع في واجهة عامة بعلامة covariant ( out)، أو contravariant ( in)، أو invariant (بدون علامة). على سبيل المثال، يمكننا تعريف واجهة من مُكرِّرات للقراءة فقط، وإعلانها كـ covariant (out) في مُعامل نوعها.IEnumerator<T>

واجهة IEnumerator < out T > { T Current { get ; } bool MoveNext (); }

مع هذا الإعلان، IEnumeratorسيتم التعامل مع على أنه متغير مشترك في معلمة نوعه، على سبيل المثال هو نوع فرعي من .IEnumerator<Cat>IEnumerator<Animal>

يفرض مدقق الأنواع أن يذكر كل تعريف دالة في واجهة ما معاملات النوع بطريقة تتوافق مع التعليقات التوضيحية in/ out. أي أنه لا يجوز أن يظهر معامل تم تعريفه كمتغير مشترك في أي موضع متغير عكسي (حيث يكون الموضع متغيرًا عكسيًا إذا ظهر تحت عدد فردي من مُنشئات النوع المتغيرة عكسيًا). القاعدة الدقيقة [ 13 ] [ 14 ] هي أن أنواع الإرجاع لجميع الدوال في الواجهة يجب أن تكون صالحة بشكل مشترك، وأن جميع أنواع معاملات الدوال يجب أن تكون صالحة بشكل عكسي ، حيث يُعرَّف "صالح بشكل مشترك" كما يلي:

  • الأنواع غير العامة (الفئات، والهياكل، والتعدادات، وما إلى ذلك) صالحة في كل من التباين المشترك والتباين العكسي.
  • يكون معامل النوع Tصالحًا بشكل متزامن إذا لم يتم وضع علامة عليه in، وصالحًا بشكل عكسي إذا لم يتم وضع علامة عليه out.
  • يكون نوع المصفوفة صالحًا من الناحية القياسية إذا كان كذلك. (وذلك لأن لغة C# تحتوي على مصفوفات متغيرة.)A[]A
  • يكون النوع العام صالحًا من النوع S-ly إذا كان لكل مُعامل ، G<A1,A2,...,An>Ai
    • Ai صالحة من النوع S-ly، ويُعلن أن المعامل iG متغير مشترك، أو
    • Ai صالحة (ليست S)-ly، ويُعلن أن المعامل iG متغاير عكسيًا، أو
    • Ai صالحة من حيث التغاير المشترك والتغاير العكسي، ويتم الإعلان عن المعلمة iG بأنها ثابتة.

كمثال على كيفية تطبيق هذه القواعد، انظر إلى واجهة المستخدم.IList<T>

interface IList <T> { void Insert ( int index , T item ) ; IEnumerator <T> GetEnumerator ( ) ; }

Tيجب أن يكون نوع المعامل Insertصالحًا في حالة التباين العكسي، أي Tيجب ألا يكون مُعَلَّمًا out. وبالمثل، يجب أن يكون نوع النتيجة صالحًا في حالة التباين المشترك، أي (بما أن الواجهة متغايرة) يجب أن يكون النوع صالحًا في حالة التباين المشترك، أي يجب ألا يكون مُعَلَّمًا . هذا يُبين أنه لا يُسمح بتصنيف الواجهة على أنها متغايرة أو متغايرة عكسيًا.IEnumerator<T>GetEnumeratorIEnumeratorTTinIList

في الحالة الشائعة لهيكل بيانات عام مثل IList، تعني هذه القيود أنه outلا يمكن استخدام المعلمة إلا للطرق التي تستخرج البيانات من الهيكل، ولا inيمكن استخدام المعلمة إلا للطرق التي تضع البيانات في الهيكل، ومن هنا جاء اختيار الكلمات الرئيسية.

بيانات

تسمح لغة C# بإضافة تعليقات توضيحية للتباين على معلمات الواجهات، ولكن ليس على معلمات الفئات. ولأن الحقول في فئات C# قابلة للتغيير دائمًا، فإن الفئات ذات المعلمات المتغيرة في C# لن تكون مفيدة جدًا. لكن اللغات التي تُركز على البيانات غير القابلة للتغيير يمكنها الاستفادة بشكل جيد من أنواع البيانات المتغيرة. على سبيل المثال، في جميع لغات Scala و Kotlin و OCaml، يكون نوع القائمة غير القابلة للتغيير متغيرًا: وهو نوع فرعي من .List[Cat]List[Animal]

تتشابه قواعد Scala للتحقق من تعليقات التباين بشكل أساسي مع قواعد C#. مع ذلك، توجد بعض الاصطلاحات التي تنطبق على هياكل البيانات غير القابلة للتغيير تحديدًا. يوضح تعريف الفئة التالي (المقتطف من التعريف) هذه الاصطلاحات .List[A]

فئة مجردة مغلقة List [ + A ] تمتد من AbstractSeq [ A ] { def head : A def tail : List [ A ] }/** يضيف عنصرًا في بداية هذه القائمة. */ def :: [ B >: A ] ( x : B ): List [ B ] = new scala . collection . immutable . :: ( x , this ) /** ... */ }

أولًا، يجب أن تكون عناصر الفئة التي لها نوع متغير غير قابلة للتغيير. هنا، headالنوع هو A، والذي تم تعريفه كمتغير مشترك ( +headوتم تعريفه بالفعل كدالة ( def). محاولة تعريفه كحقل قابل للتغيير ( var) سترفض باعتباره خطأ في النوع.

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

def :: ( x : A ): List [ A ]

مع ذلك، يُعدّ هذا خطأً في النوع، لأنّ المعامل المتغير المشترك Aيظهر في موضع متغير عكسي (كمعامل دالة). لكن توجد حيلة لتجاوز هذه المشكلة. نُعرّف ::نوعًا أكثر عمومية، يسمح بإضافة عنصر من أي نوع B طالما Bأنه نوع فائق لـ A. لاحظ أن هذا يعتمد على Listكون متغيرًا مشتركًا، لأنّ this له نوع ونتعامل معه على أنه من النوع . قد لا يكون من الواضح للوهلة الأولى أن النوع المُعمّم سليم، ولكن إذا بدأ المبرمج بتعريف النوع الأبسط، فستُشير أخطاء النوع إلى الموضع الذي يحتاج إلى التعميم.List[A]List[B]

استنتاج التباين

من الممكن تصميم نظام أنواع يستنتج فيه المترجم تلقائيًا أفضل تعليقات التباين الممكنة لجميع معلمات أنواع البيانات. [ 15 ] مع ذلك، قد يصبح التحليل معقدًا لعدة أسباب. أولًا، التحليل غير محلي لأن تباين واجهة ما يعتمد على تباين جميع الواجهات التي تشير إليها. ثانيًا، للحصول على أفضل الحلول الفريدة، يجب أن يسمح نظام الأنواع بمعلمات ثنائية التباين (المتغيرة معًا والمتغيرة عكسيًا في آن واحد). وأخيرًا، ينبغي أن يكون تباين معلمات الأنواع خيارًا مقصودًا من مصمم الواجهة، وليس أمرًا يحدث صدفةً.II

لهذه الأسباب [ 16 لا تستخدم معظم لغات البرمجة استنتاج التباين إلا بشكل محدود للغاية. فلغتا C# وScala لا تستنتجان أي تعليقات توضيحية للتباين على الإطلاق. بينما تستطيع لغة OCaml استنتاج تباين أنواع البيانات الملموسة ذات المعاملات، إلا أنه يتعين على المبرمج تحديد تباين الأنواع المجردة (الواجهات) بشكل صريح.

على سبيل المثال، لنفترض نوع بيانات OCaml Tالذي يغلف دالة

type ( ' a , ' b ) t = T of ( ' a -> ' b )

سيستنتج المترجم تلقائيًا أن Tالدالة متغايرة عكسيًا في المعامل الأول، ومتغايرة مصاحبة في المعامل الثاني. كما يمكن للمبرمج تقديم تعليقات توضيحية صريحة، والتي سيتحقق المترجم من استيفائها. وبالتالي، فإن التصريح التالي مكافئ للتصريح السابق:

نوع (- ' أ ، + ' ب ) ت = T من ( ' أ -> ' ب )

تُصبح التعليقات التوضيحية الصريحة في لغة OCaml مفيدة عند تحديد الواجهات. على سبيل المثال، تتضمن واجهة المكتبة القياسية لجداول الارتباط تعليقًا توضيحيًا يُشير إلى أن مُنشئ نوع الخريطة مُتغير في نوع النتيجة.Map.S

نوع الوحدة S = نوع الإشارة نوع المفتاح (+ ' a ) قيمة فارغة : ' a قيمة الذاكرة : مفتاح - > ' a قيمة - > منطقي ... نهاية

وهذا يضمن أن يكون eg نوعًا فرعيًا من .catIntMap.tanimalIntMap.t

تعليقات توضيحية لتباين مواقع الاستخدام (أحرف البدل)

من عيوب أسلوب موقع التصريح أن العديد من أنواع الواجهات يجب أن تكون ثابتة. على سبيل المثال، رأينا سابقًا أن IListيجب أن تكون ثابتة، لأنها تحتوي على كل من Insertو GetEnumerator. ولإظهار المزيد من التنوع، يمكن لمصمم واجهة برمجة التطبيقات توفير واجهات إضافية تُقدم مجموعات فرعية من الطرق المتاحة (مثل "قائمة إدراج فقط" التي تُقدم فقط Insert). إلا أن هذا الأسلوب يصبح معقدًا بسرعة.

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

توفر لغة جافا تعليقات توضيحية لتباين موقع الاستخدام من خلال الأحرف البديلة ، وهي شكل مقيد من الأنواع الوجودية المحدودة . يمكن إنشاء نوع مُعامل باستخدام حرف بديل مع حد أعلى أو أدنى، على سبيل المثال `<n>` أو `<n>`. الحرف البديل غير المحدود مثل `<n>` يُكافئ `<n>` . يُمثل هذا النوع ` <n> ` نوعًا غير معروف يُحقق الحد. [ 3 ] : 139 على سبيل المثال، إذا كان `<n>` من النوع `<n>` ، فسيقبل مدقق الأنواع `<n>`.?List<?extendsAnimal>List<?superAnimal>List<?>List<?extendsObject>List<X>XlList<?extendsAnimal>

Animal a = l . get ( 3 );

لأن النوع معروف بأنه نوع فرعي من ، ولكنXAnimal

l.add ( new Animal ( ) );

سيتم رفض هذا كخطأ في النوع، لأن `an` ليس بالضرورة `an` . بشكل عام، عند وجود واجهة `a` ، فإن الإشارة إلى `an` تمنع استخدام طرق من تلك الواجهة حيث يظهر `an` بشكل متناقض في نوع الطريقة. على العكس من ذلك، إذا كان `an` من النوع ` a`، فيمكن استدعاء `an` ولكن لا يمكن استدعاء `an` .AnimalXI<T>I<?extendsT>TlList<?superAnimal>l.addl.get

يمكن تصور التنميط الفرعي باستخدام الأحرف البديلة في لغة جافا على شكل مكعب.

بينما تُعتبر الأنواع المُعاملة غير المُستخدمة للأحرف البديلة في جافا ثابتة (على سبيل المثال، لا توجد علاقة فرعية بين `<<>` و `<< >`)، يُمكن جعل الأنواع المُستخدمة للأحرف البديلة أكثر تحديدًا بتحديد حدٍّ أدق. على سبيل المثال، `<<<> ` هو نوع فرعي من `<<< >` . يُبيّن هذا أن الأنواع المُستخدمة للأحرف البديلة مُتغيرة في حدودها العليا (ومُتغيرة عكسيًا في حدودها الدنيا ). إجمالًا، عند وجود نوع مُستخدم للأحرف البديلة مثل `<<<>` ، توجد ثلاث طرق لتكوين نوع فرعي: بتخصيص الفئة `<<<<>` ، أو بتحديد حدٍّ أدق ، أو باستبدال الحرف البديل بنوع مُحدد (انظر الشكل). [ 3 ] : 139List<Cat>List<Animal>List<?extendsCat>List<?extendsAnimal>C<?extendsT>CT?

بتطبيق شكلين من الأشكال الثلاثة المذكورة أعلاه للتصنيف الفرعي، يصبح من الممكن، على سبيل المثال، تمرير وسيط من نوع إلى دالة تتوقع نوعًا آخر . هذا هو نوع التعبير الناتج عن أنواع الواجهات المتغيرة. يعمل النوع كنوع واجهة يحتوي فقط على الدوال المتغيرة للنوع ، ولكن لم يكن على مُنفِّذ النوع تعريفه مسبقًا.List<Cat>List<?extendsAnimal>List<?extendsAnimal>List<T>List<T>

في الحالة الشائعة لبنية بيانات عامة IList، تُستخدم المعاملات المتغيرة مع الزمن للطرق التي تستخرج البيانات من البنية، والمعاملات المتغيرة عكسيًا للطرق التي تُدخل البيانات إلى البنية. ويُسهّل الاختصار " PECOSS" (المنتج يمتد، المستهلك يتفوق) من كتاب "Effective Java" لجوشوا بلوخ، تذكّر متى يُستخدم التغاير والتغاير العكسي. [ 3 ] : 141

تتميز الأحرف البديلة بالمرونة، ولكن لها عيب. فبينما يعني اختلاف موقع الاستخدام أن مصممي واجهات برمجة التطبيقات ليسوا بحاجة إلى مراعاة اختلاف معلمات النوع للواجهات، إلا أنهم غالبًا ما يضطرون إلى استخدام توقيعات طرق أكثر تعقيدًا. ومن الأمثلة الشائعة على ذلك الواجهة Comparable[ 3 ] : 66. لنفترض أننا نريد كتابة دالة لإيجاد أكبر عنصر في مجموعة. يجب أن تُنفذ العناصر الطريقة [ 3 ] : 66، لذا قد تكون المحاولة الأولى كالتالي:compareTo

< T extends Comparable < T >> T max ( Collection < T > coll );

مع ذلك، هذا النوع ليس عامًا بما فيه الكفاية ؛ إذ يمكن إيجاد القيمة القصوى لـ a ، ولكن ليس لـ . تكمن المشكلة في أن لا يُطبّق واجهة ، بل واجهة (أفضل) . في جافا، على عكس سي شارب، لا يُعتبر نوعًا فرعيًا من . بدلًا من ذلك ، يجب تعديل نوع .Collection<Calendar>Collection<GregorianCalendar>GregorianCalendarComparable<GregorianCalendar>Comparable<Calendar>Comparable<Calendar>Comparable<GregorianCalendar>max

< T extends Comparable <? super T >> T max ( Collection < T > coll );

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

يمكن تغيير الطريقة بشكل أكبر باستخدام حرف بدل ذي حد أعلى لمعامل الطريقة: [ 17 ]max

< T extends Comparable <? super T >> T max ( Collection <? extends T > coll );

مقارنة التعليقات التوضيحية لموقع الإعلان وموقع الاستخدام

تُوفر تعليقات التباين في مواقع الاستخدام مرونة إضافية، مما يسمح لعدد أكبر من البرامج بالتحقق من أنواع البيانات. ومع ذلك، فقد وُجهت إليها انتقادات بسبب التعقيد الذي تُضيفه إلى اللغة، مما يؤدي إلى توقيعات أنواع معقدة ورسائل خطأ معقدة.

إحدى طرق تقييم مدى فائدة المرونة الإضافية هي التحقق من استخدامها في البرامج الحالية. فقد أظهرت دراسة استقصائية لمجموعة كبيرة من مكتبات جافا [ 15 ] أن 39% من التعليقات التوضيحية ذات الأحرف البديلة كان من الممكن استبدالها مباشرةً بتعليقات توضيحية في مواقع التعريف. وبالتالي، فإن النسبة المتبقية البالغة 61% تُشير إلى المواضع التي تستفيد فيها جافا من توفر نظام مواقع الاستخدام.

في لغات البرمجة التي تعتمد على موقع التصريح، يجب على المكتبات إما تقليل التباين أو تعريف المزيد من الواجهات. على سبيل المثال، تُعرّف مكتبة Scala Collections ثلاث واجهات منفصلة للفئات التي تستخدم التغاير: واجهة أساسية متغايرة تحتوي على طرق مشتركة، ونسخة ثابتة قابلة للتغيير تُضيف طرقًا ذات تأثيرات جانبية، ونسخة متغايرة غير قابلة للتغيير قد تُخصّص التطبيقات الموروثة للاستفادة من المشاركة الهيكلية. [ 18 ] يتوافق هذا التصميم جيدًا مع التعليقات التوضيحية لموقع التصريح، ولكن العدد الكبير من الواجهات يُضيف تعقيدًا لمستخدمي المكتبة. وقد لا يكون تعديل واجهة المكتبة خيارًا متاحًا ، لا سيما أن أحد أهداف إضافة الأنواع العامة إلى Java كان الحفاظ على التوافق الثنائي مع الإصدارات السابقة.

من جهة أخرى، تُعدّ الأحرف البديلة في جافا معقدة بحد ذاتها. في عرض تقديمي في مؤتمر [ 19 ] ، انتقدها جوشوا بلوخ لكونها صعبة الفهم والاستخدام، مصرحًا بأنه عند إضافة دعم للإغلاقات "لا يمكننا ببساطة تحمل إضافة أحرف بديلة أخرى ". استخدمت الإصدارات الأولى من سكالا تعليقات توضيحية للتباين في موقع الاستخدام، لكن المبرمجين وجدوا صعوبة في استخدامها عمليًا، بينما وُجد أن التعليقات التوضيحية في موقع الإعلان مفيدة جدًا عند تصميم الفئات. [ 20 ] أضافت الإصدارات اللاحقة من سكالا أنواعًا وجودية وأحرفًا بديلة على غرار جافا؛ ومع ذلك، وفقًا لمارتن أوديرسكي ، لو لم تكن هناك حاجة للتوافق مع جافا، لما تم تضمينها على الأرجح. [ 21 ]

يجادل روس تيت [ 22 ] بأن جزءًا من تعقيد أحرف البدل في جافا يعود إلى قرار ترميز التباين في موقع الاستخدام باستخدام شكل من أشكال الأنواع الوجودية. استخدمت المقترحات الأصلية [ 23 ] [ 24 ] صيغة خاصة لتعليقات التباين، بدلاً من صيغة جافا الأكثر تفصيلاً .List<+Animal>List<?extendsAnimal>

بما أن الأحرف البديلة تُعدّ نوعًا من أنواع الوجود، فيمكن استخدامها لأغراضٍ تتجاوز مجرد التباين. يسمح نوعٌ مثل ("قائمة من نوع غير معروف" [ 25 ] ) بتمرير الكائنات إلى الدوال أو تخزينها في الحقول دون تحديد معلمات النوع بدقة. يُعدّ هذا مفيدًا بشكلٍ خاص للفئات التي لا تُشير معظم دوالها إلى معلمات النوع.List<?>Class

مع ذلك، يُعدّ استنتاج أنواع البيانات الوجودية مشكلة معقدة. بالنسبة لمُطوّر المُصرّف، تُثير الأحرف البديلة في جافا مشاكل تتعلق بإنهاء مُدقّق الأنواع، واستنتاج وسيطات الأنواع، والبرامج الغامضة. [ 26 ] بشكل عام، من غير الممكن تحديد ما إذا كان برنامج جافا الذي يستخدم الأنواع العامة مُعرّفًا بشكل صحيح أم لا، [ 27 ] لذا سيضطر أي مُدقّق أنواع إلى الدخول في حلقة لا نهائية أو تجاوز المهلة الزمنية لبعض البرامج. بالنسبة للمُبرمج، يؤدي ذلك إلى رسائل خطأ مُعقّدة. يتحقق جافا من أنواع الأحرف البديلة عن طريق استبدالها بمتغيرات أنواع جديدة (ما يُسمى بتحويل الالتقاط ). قد يُصعّب هذا قراءة رسائل الخطأ، لأنها تُشير إلى متغيرات أنواع لم يكتبها المُبرمج مُباشرةً. على سبيل المثال، ستؤدي مُحاولة إضافة متغير إلى متغير آخر إلى ظهور خطأ مثلCatList<?extendsAnimal>

لا يمكن تطبيق طريقة List.add (capture#1). (لا يمكن تحويل الوسيط الفعلي Cat إلى capture#1 عن طريق تحويل استدعاء الأسلوب) حيث يمثل capture#1 متغير نوع جديد: يوسع الالتقاط رقم 1 نطاق الحيوان من التقاط ؟ يمتد نطاق الحيوان

بما أن كلاً من التعليقات التوضيحية في موقع الإعلان والتعليقات التوضيحية في موقع الاستخدام قد تكون مفيدة، فإن بعض أنظمة الأنواع توفر كليهما. [ 15 ] [ 22 ]

انظر أيضاً

ملحوظات

  1. يحدث هذا فقط في حالة شاذة. على سبيل المثال،I<T> = intيمكن وضع أي نوع في المتغيرTوستظل النتيجة كما هيint.

مراجع

  1. 1 2 3 سكيت، جون (23 مارس 2019). لغة سي شارب بتفصيل . مانينغ. ISBN 978-1617294532.
  2. مندوب الدالة <T, TResult> - وثائق MSDN
  3. 1 2 3 4 5 6 7 8 بلوخ، جوشوا (2018). "جافا الفعّالة: دليل لغة البرمجة" ( الطبعة الثالثة). أديسون-ويسلي. ISBN  978-0134685991.
  4. رينولدز، جون سي. (1981). جوهر لغة ألغول . ندوة حول اللغات الخوارزمية. نورث هولاند.
  5. كارديلي، لوكا (1984). دلالات التوريث المتعدد (ملف PDF) . دلالات أنواع البيانات (الندوة الدولية، صوفيا أنتيبوليس، فرنسا، 27-29 يونيو 1984). سلسلة محاضرات في علوم الحاسوب. المجلد 173. سبرينغر. الصفحات 51-67 . doi : 10.1007/3-540-13346-1_2 . ISBN   3-540-13346-1.نسخة أطول: (فبراير 1988). "دلالات التوريث المتعدد". المعلومات والحوسبة . 76 (2/3): 138-164 . CiteSeerX 10.1.1.116.1298 . doi : 10.1016/0890-5401(88)90007-7 . 
  6. ^ تورجيرسن ، مادس. "C # 9.0 في السجل" .
  7. أليسون، تشاك. "ما الجديد في لغة C++ القياسية؟" .
  8. "إصلاح مشاكل الأنواع الشائعة" . لغة برمجة دارت .
  9. برتراند ماير (أكتوبر 1995). "الكتابة الثابتة" (ملف PDF) . OOPSLA 95 (البرمجة الكائنية، والأنظمة، واللغات، والتطبيقات)، أتلانتا، 1995 .
  10. ١ ٢ هوارد، مارك؛ بيزولت، إريك؛ ماير، برتراند؛ كولنيه، دومينيك؛ ستاف، إيمانويل؛ أرنو، كارين؛ كيلر، ماركوس (أبريل ٢٠٠٣). "التغاير الآمن من حيث النوع: يمكن للمترجمات الكفؤة التقاط جميع استدعاءات catcall" (ملف PDF) . تم الاطلاع عليه بتاريخ ٢٣ مايو ٢٠١٣ .
  11. فرانز ويبر (1992). "تحقيق التكافؤ بين صحة الفئة وصحة النظام - كيفية ضبط التغاير". TOOLS 8 (المؤتمر الثامن حول تكنولوجيا لغات وأنظمة البرمجة الكائنية)، دورتموند، 1992. CiteSeerX 10.1.1.52.7872 . 
  12. كاستاغنا، جوزيبي (مايو 1995). "التغاير والتغاير العكسي: صراع بلا سبب". معاملات ACM في لغات البرمجة والأنظمة . 17 (3): 431-447 . CiteSeerX 10.1.1.115.5992 . doi : 10.1145/203095.203096 . S2CID 15402223 .  
  13. ليبرت، إريك (3 ديسمبر 2009). "قواعد دقيقة لصحة التباين" . تم الاسترجاع في 16 أغسطس 2016 .
  14. "القسم الثاني.9.7". معيار ECMA الدولي ECMA-335 البنية التحتية للغة المشتركة (CLI) (الطبعة السادسة ). يونيو 2012. 
  15. 1 2 3 ألتيدور، جون؛ شان، هوانغ شان؛ سماراغداكيس، يانيس (2011). "السيطرة على المتغيرات غير المحددة: الجمع بين تباين موقع التعريف وموقع الاستخدام". وقائع المؤتمر الثاني والثلاثين لجمعية ACM SIGPLAN حول تصميم لغات البرمجة وتنفيذها (PLDI'11) . ACM. الصفحات 602-613 . CiteSeerX 10.1.1.225.8265 . doi : 10.1145/1993316.1993569 . ISBN   9781450306638.
  16. ليبرت، إريك (29 أكتوبر 2007). "التغاير والتغاير العكسي في لغة سي شارب، الجزء السابع: لماذا نحتاج إلى صيغة على الإطلاق؟" . تم الاطلاع عليه بتاريخ 16 أغسطس 2016 .
  17. بلوخ 2018 ، الصفحات 139-145، الفصل §5 البند 31: استخدام الأحرف البديلة المحدودة لزيادة مرونة واجهة برمجة التطبيقات.
  18. أوديرسكي، مارين؛ سبون، ليكس (7 سبتمبر 2010). "واجهة برمجة تطبيقات المجموعات في سكالا 2.8" . تم الاطلاع عليه بتاريخ 16 أغسطس 2016 .
  19. بلوخ، جوشوا (نوفمبر 2007). "جدل الإغلاقات [ فيديو ] " . عرض تقديمي في مؤتمر جافابوليس 2007. مؤرشف من الأصل بتاريخ 2014-02-02.{{cite web}}: CS1 maint: location ( link )
  20. أوديرسكي، مارتن؛ زينجر، ماتياس (2005). "تجريدات المكونات القابلة للتوسع" (ملف PDF) . وقائع المؤتمر السنوي العشرين لجمعية ACM SIGPLAN حول البرمجة الكائنية والأنظمة واللغات والتطبيقات (OOPSLA '05) . ACM. الصفحات 41-57 . CiteSeerX 10.1.1.176.5313 . doi : 10.1145/1094811.1094815 . ISBN   1595930310.
  21. فينرز، بيل؛ سومرز، فرانك (18 مايو 2009). "الغرض من نظام أنواع سكالا: محادثة مع مارتن أوديرسكي، الجزء الثالث" . تم الاطلاع عليه في 16 أغسطس 2016 .
  22. 1 2 تيت، روس (2013). "تباين الموقع المختلط" . FOOL '13: وقائع غير رسمية لورشة العمل الدولية العشرين حول أسس لغات البرمجة الكائنية التوجه . CiteSeerX 10.1.1.353.4691 . 
  23. إيغاراشي، أتسوكي؛ فيرولي، ميركو (2002). "حول التنميط الفرعي القائم على التباين للأنواع البارامترية". وقائع المؤتمر الأوروبي السادس عشر حول البرمجة الكائنية التوجه (ECOOP '02) . سلسلة محاضرات في علوم الحاسوب. المجلد 2374. الصفحات 441-469 . CiteSeerX 10.1.1.66.450 . doi : 10.1007/3-540-47993-7_19 . ISBN    3-540-47993-7.
  24. ثورب، كريستن كراب؛ تورجرسن، مادز (1999). "توحيد العمومية: الجمع بين مزايا الأنواع الافتراضية والفئات المُعَلمة". البرمجة الكائنية التوجه (ECOOP '99) . سلسلة محاضرات في علوم الحاسوب. المجلد 1628. سبرينغر. الصفحات 186-204 . CiteSeerX 10.1.1.91.9795 . doi : 10.1007/3-540-48743-3_9 . ISBN    3-540-48743-3.
  25. "دروس جافا™ التعليمية، الأنواع العامة (محدثة)، الأحرف البديلة غير المحدودة" . تم الاطلاع عليه في 17 يوليو 2020 .
  26. تيت، روس؛ ليونغ، آلان؛ ليرنر، سورين (2011). "التحكم في الأحرف البديلة في نظام أنواع جافا" . وقائع المؤتمر الثاني والثلاثين لجمعية ACM SIGPLAN حول تصميم لغات البرمجة وتنفيذها (PLDI '11) . الصفحات 614-627 . CiteSeerX 10.1.1.739.5439 . ISBN   9781450306638.
  27. غريغور، رادو (2017). "الأنواع العامة في جافا كاملة تورينغ". وقائع ندوة ACM SIGPLAN الرابعة والأربعين حول مبادئ لغات البرمجة (POPL'17) . الصفحات 73-85 . arXiv : 1605.05274 . Bibcode : 2016arXiv160505274G . ISBN  9781450346603.