Generics في Go: لماذا هي ضرورية وكيفية استخدامها للمبتدئين

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

مقدمة: ما هي Generics ولماذا ظهرت في Go؟

\n

لفترة طويلة، ظلت لغة Go (Golang) واحدة من اللغات الحديثة القليلة التي لا تدعم البرمجة العامة (generics). كان على المطورين إما تكرار الكود لأنواع بيانات مختلفة، أو استخدام الواجهات (interfaces) و interface{} الفارغة، مما أدى إلى فقدان أمان الأنواع والحاجة إلى إجراء تحويل الأنواع في وقت التشغيل.

\n

مع إصدار Go 1.18 (فبراير 2022)، ظهرت الأدوية الجنيسة التي طال انتظارها في اللغة. Generics هي آلية تسمح بكتابة دوال وأنواع وهياكل بيانات تعمل مع أنواع عشوائية، مع الحفاظ على الكتابة الصارمة للأنواع في مرحلة الترجمة.

\n

في هذه المقالة، سنشرح:

\n
    \n
  • لماذا نحتاج إلى الأدوية الجنيسة في Go؛
  • \n
  • كيفية تعريف الدوال والأنواع العامة؛
  • \n
  • ما هي القيود (constraints) وكيفية استخدامها؛
  • \n
  • أمثلة عملية للتطبيق.
  • \n
\n\n

لماذا نحتاج إلى Generics في Go؟

\n

قبل ظهور الأدوية الجنيسة، كانت المشكلة النموذجية تبدو كالتالي: الحاجة إلى كتابة دالة تعمل مع شرائح (slices) من أنواع مختلفة (مثل int، string، float64).

\n

بدون الأدوية الجنيسة، كان لا بد إما من كتابة ثلاث دوال منفصلة:

\n
func SumInts(nums []int) int {\n    total := 0\n    for _, v := range nums {\n        total += v\n    }\n    return total\n}\n\nfunc SumFloats(nums []float64) float64 {\n    total := 0.0\n    for _, v := range nums {\n        total += v\n    }\n    return total\n}\n
\n

أو استخدام interface{} مع تحويل الأنواع - وهو أمر غير آمن وغير مريح.

\n

مع الأدوية الجنيسة، تكفي دالة واحدة:

\n
func Sum[T int | float64](nums []T) T {\n    var total T\n    for _, v := range nums {\n        total += v\n    }\n    return total\n}\n
\n

المزايا واضحة:

\n
    \n
  • أمان الأنواع - يتم اكتشاف أخطاء الأنواع في مرحلة الترجمة؛
  • \n
  • تقليل تكرار الكود - دالة واحدة لأنواع متعددة؛
  • \n
  • قابلية القراءة - يصبح الكود أكثر إيجازاً ووضوحاً.
  • \n
\n\n

أساسيات بناء جملة Generics في Go

\n

تعريف دالة عامة

\n

يستخدم بناء الجملة الأساسي الأقواس المربعة [] لتحديد معاملات النوع:

\n
func اسمالدالة[اسمالنوع constraint](المعاملات) النتيجة {\n    // جسم الدالة\n}\n
\n

مثال لدالة بسيطة تعيد القيمة التي تم تمريرها:

\n
func Identity[T any](value T) T {\n    return value\n}\n\nfunc main() {\n    fmt.Println(Identity[int](42))       // 42\n    fmt.Println(Identity[string]("Go"))  // Go\n}\n
\n

يمكن لـ Go استنتاج النوع تلقائياً، لذا فإن التحديد الصريح [int] ليس ضرورياً:

\n
fmt.Println(Identity(42))       // Go سيفهم أن T = int\nfmt.Println(Identity("Go"))    // T = string\n
\n\n

الأنواع العامة (الهياكل)

\n

تعمل الأدوية الجنيسة ليس فقط مع الدوال، ولكن أيضاً مع الأنواع المخصصة:

\n
type Stack[T any] struct {\n    elements []T\n}\n\nfunc (s *Stack[T]) Push(value T) {\n    s.elements = append(s.elements, value)\n}\n\nfunc (s *Stack[T]) Pop() (T, bool) {\n    if len(s.elements) == 0 {\n        var zero T\n        return zero, false\n    }\n    value := s.elements[len(s.elements)-1]\n    s.elements = s.elements[:len(s.elements)-1]\n    return value, true\n}\n\nfunc main() {\n    intStack := Stack[int]{}\n    intStack.Push(10)\n    intStack.Push(20)\n    val, ok := intStack.Pop()\n    fmt.Println(val, ok) // 20 true\n}\n
\n\n

القيود (Constraints): تحديد أنواع محددة

\n

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

\n

مدمجة

المدونات

توصيات الكتب