Skip to content

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

الأنواع العامة، أو بالاسم الأكثر أكاديمية - تعدد الأشكال المعلماتي (Parameterized Polymorphism)، هو تحقيق إعادة استخدام الكود والمرونة من خلال معلمات الأنواع. في العديد من لغات البرمجة، يعد تعدد الأشكال المعلماتي مفهوماً مهماً، فهو يجعل الدوال أو هياكل البيانات قادرة على معالجة بيانات من أنواع مختلفة دون الحاجة لكتابة كود منفصل لكل نوع. في البداية لم يكن لدى Go مفهوم الأنواع العامة، لكن منذ ولادتها، كان الأمر الأكثر نداءً في مجتمع Go هو إضافة الأنواع العامة، وأخيراً أضافت لغة Go دعماً للأنواع العامة في النسخة 1.18 عام 2022.

التصميم

عند تصميم الأنواع العامة في لغة Go، تم النظر في المخططات التالية

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

تمثل الطريقتان أعلاه طرفين متطرفين، والمخطط الذي اختارته لغة Go في النهاية هو Gcshape stenciling، وهو اختيار وسطي، حيث يتم استخدام أحادية الشكل للأنواع التي لها نفس شكل الذاكرة، وتستخدم القواميس في وقت التشغيل للحصول على معلومات النوع.

مقدمة

لنبدأ بمثال بسيط.

go
func Sum(a, b int) int {
   return a + b
}

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

الصيغة

تكتب الأنواع العامة كالتالي

go
func Sum[T int | float64](a, b T) T {
   return a + b
}

معامل النوع: T هو معامل نوع، نوعه المحدد يعتمد على ما يتم تمريره

قيد النوع: int | float64 يشكل قيد نوع، يحدد الأنواع المسموح بها

وسيط النوع: Sum[int](1,2)، تحديد int يدوياً، أو يمكن ترك المترجم يستنتجه تلقائياً

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

شريحة عامة

go
type GenericSlice[T int | int32 | int64] []T

خريطة عامة

go
type GenericMap[K comparable, V int | string | byte] map[K]V

هيكل عام

go
type GenericStruct[T int | string] struct {
   Name string
   Id   T
}

واجهة عامة

go
type SayAble[T int | string] interface {
   Say() T
}

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

بعد النسخة 1.18، تغير تعريف الواجهة إلى مجموعة أنواع (type set). الواجهة التي تحتوي على مجموعة أنواع تسمى واجهة عامة (General Interface).

الاتحاد

go
type SignedInt interface {
   int8 | int16 | int | int32 | int64
}

التقاطع

go
type Number interface {
  SignedInt
  Integer
}

المجموعة الفارغة

go
type Integer interface {
  SignedInt
  UnsignedInt
}

الواجهة الفارغة

الواجهة الفارغة هي مجموعة جميع الأنواع.

go
func Do[T interface{}](n T) T {
   return n
}

عادة نستخدم any بدلاً من interface{}.

النوع الأساسي

استخدام ~ للإشارة إلى النوع الأساسي.

go
type Int interface {
   ~int8 | ~int16 | ~int | ~int32 | ~int64
}

نقاط مهمة

لا يمكن استخدام النوع العام كنوع أساسي

go
type GenericType[T int | int32 | int64] T // خطأ

لا يمكن استخدام تأكيد النوع مع الأنواع العامة

go
func Sum[T int | float64](a, b T) T {
   ints,ok := a.(int) // غير مسموح
   return a + b
}

الهياكل المجهولة لا تدعم الأنواع العامة

الدوال المجهولة لا تدعم الأنواع العامة المخصصة

لا يتم دعم الطرق العامة

مجموعة الأنواع لا يمكن استخدامها كوسيط نوع

الواجهة comparable لا يمكن دمجها في مجموعة الأنواع

الاستخدام

قائمة الانتظار

go
type Queue[T any] []T

func (q *Queue[T]) Push(e T) {
  *q = append(*q, e)
}

func (q *Queue[T]) Pop(e T) (_ T) {
  if q.Size() > 0 {
    res := q.Peek()
    *q = (*q)[1:]
    return res
  }
  return
}

func (q *Queue[T]) Peek() (_ T) {
  if q.Size() > 0 {
    return (*q)[0]
  }
  return
}

func (q *Queue[T]) Size() int {
  return len(*q)
}

الكومة

go
type Comparator[T any] func(a, b T) int

type BinaryHeap[T any] struct {
  s []T
  c Comparator[T]
}

تجمع الكائنات

go
func NewPool[T any](newFn func() T) *Pool[T] {
	return &Pool[T]{
		pool: &sync.Pool{
			New: func() interface{} {
				return newFn()
			},
		},
	}
}

type Pool[T any] struct {
	pool *sync.Pool
}

func (p *Pool[T]) Put(v T) {
	p.pool.Put(v)
}

func (p *Pool[T]) Get() T {
	var v T
	get := p.pool.Get()
	if get != nil {
		v, _ = get.(T)
	}
	return v
}

خلاصة

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

Golang تم تحريره بواسطة www.golangdev.cn