قبل Go 1.18، كان Go يدعم الأجناس العامة المدمجة فقط. منذ Go 1.18، أصبح Go يدعم الأجناس العامة المخصّصة أيضاً. لا يقدّم هذا المقال سوى الأجناس العامة المدمجة.
تُدعَم أنواع الأجناس العامة المدمجة في Go عبر أنواع مركّبة (composite types) من مواطني الدرجة الأولى. يمكننا استخدام الأنواع المركّبة لإنشاء عدد لا نهائي من الأنواع المخصّصة باستخدام الأنواع المركّبة. سيعرض هذا المقال بعض أمثلة تركيب الأنواع ويشرح كيفية قراءة هذه الأنواع المركّبة.
أمثلة على تركيب الأنواع
صُمّمت عمليات تركيب الأنواع في Go لتكون بديهية جداً وسهلة التفسير. من الصعب أن تضيع في فهم الأنواع المركّبة في Go، حتى في حالة بعضها شديد التعقيد. سيسرد ما يلي عدة أمثلة على تركيب الأنواع، من الأبسط إلى الأكثر تعقيداً.
لنلقِ نظرة على قيمة حرفية لنوع مركّب بسيط.
[3][4]int
عند تفسير نوع مركّب، ينبغي أن ننظر إليه من اليسار إلى اليمين. تشير [3] في اليسار في القيمة الحرفية للنوع أعلاه إلى أن هذا النوع هو نوع مصفوفة. الجزء الأيمن بأكمله التالي، وهو [4]int، هو نوع مصفوفة آخر، وهو نوع عناصر نوع المصفوفة الأول. ونوع عناصر نوع العناصر (نوع مصفوفة) لنوع المصفوفة الأول هو النوع المدمج int. يمكن النظر إلى نوع المصفوفة الأول على أنه نوع مصفوفة ثنائية الأبعاد.
مثال على استخدام نوع المصفوفة ثنائية الأبعاد هذا.
package main
import (
"fmt"
)
func main() {
matrix := [3][4]int{
{1, 0, 0, 1},
{0, 1, 0, 1},
{0, 0, 1, 1},
}
matrix[1][1] = 3
a := matrix[1] // نوع a هو [4]int
fmt.Println(a) // [0 3 0 1]
}
وبالمثل،
[][]stringنوع شريحة نوع عناصره نوع شريحة آخر[]string.**boolنوع مؤشر نوعه الأساسي نوع مؤشر آخر*bool.chan chan intنوع قناة نوع عناصره نوع قناة آخرchan int.map[int]map[int]stringنوع خريطة نوع عناصره نوع خريطة آخرmap[int]string. ونوعا مفاتيح نوعي الخريطة كلاهماint.func(int32) func(int32)نوع دالة نوع نتيجة الإرجاع الوحيدة فيه هو نوع دالة آخرfunc(int32). ولنوعي الدالتين كلٌّ منهما وسيط إدخال واحد فقط من النوعint32.
لنلقِ نظرة على نوع آخر.
chan *[16]byte
تشير الكلمة المفتاحية chan في أقصى اليسار إلى أن هذا النوع هو نوع قناة. والجزء الأيمن بأكمله *[16]byte، وهو نوع مؤشر، يدل على نوع عناصر نوع القناة هذا. والنوع الأساسي لنوع المؤشر هو [16]byte، وهو نوع مصفوفة. ونوع عناصر نوع المصفوفة هو byte.
مثال على استخدام نوع القناة هذا.
package main
import (
"fmt"
"time"
"crypto/rand"
)
func main() {
c := make(chan *[16]byte)
go func() {
// استخدم مصفوفتين لتجنّب تسابق البيانات.
var dataA, dataB = new([16]byte), new([16]byte)
for {
_, err := rand.Read(dataA[:])
if err != nil {
close(c)
} else {
c <- dataA
dataA, dataB = dataB, dataA
}
}
}()
for data := range c {
fmt.Println((*data)[:])
time.Sleep(time.Second / 2)
}
}
وبالمثل، النوع map[string][]func(int) int هو نوع خريطة. ونوع مفاتيح نوع الخريطة هذا هو string. والجزء الأيمن المتبقي []func(int) int يدل على نوع عناصر نوع الخريطة. وتشير [] إلى أن نوع العناصر هو نوع شريحة، نوع عناصره هو نوع دالة func(int) int.
مثال على استخدام نوع الخريطة الذي شرحناه للتو.
package main
import "fmt"
func main() {
addone := func(x int) int {return x + 1}
square := func(x int) int {return x * x}
double := func(x int) int {return x + x}
transforms := map[string][]func(int) int {
"inc,inc,inc": {addone, addone, addone},
"sqr,inc,dbl": {square, addone, double},
"dbl,sqr,sqr": {double, double, square},
}
for _, n := range []int{2, 3, 5, 7} {
fmt.Println(">>>", n)
for name, transfers := range transforms {
result := n
for _, xfer := range transfers {
result = xfer(result)
}
fmt.Printf(" %v: %v \n", name, result)
}
}
}
فيما يلي نوع يبدو معقداً إلى حد ما.
[]map[struct {
a int
b struct {
x string
y bool
}
}]interface {
Build([]byte, struct {x string; y bool}) error
Update(dt float64)
Destroy()
}
لنقرأه من اليسار إلى اليمين. تشير [] الابتدائية في أقصى اليسار إلى أن هذا النوع هو نوع شريحة. وتُظهر الكلمة المفتاحية map التالية أن نوع عناصر نوع الشريحة هو نوع خريطة. ونوع البنية الذي تدل عليه القيمة الحرفية للبنية المحصورة بين [] التي تلي الكلمة المفتاحية map هو نوع مفاتيح نوع الخريطة. ونوع عناصر نوع الخريطة هو نوع واجهة يحدد ثلاث طرق. أما نوع المفاتيح، وهو نوع بنية، فله حقلان: الحقل a من النوع int، والحقل الآخر b من نوع بنية آخر struct {x string; y bool}.
يرجى ملاحظة أن نوع البنية الثاني يُستخدم أيضاً كنوع وسيط لإحدى الطرق التي يحددها نوع الواجهة المذكور للتو.
لتحقيق قابلية قراءة أفضل، غالباً ما نفكك مثل هذا النوع إلى عدة تعريفات أنواع. والاسم المستعار للنوع T المعرّف في الشيفرة التالية والنوع المشروح أعلاه للتو يدلان على النوع نفسه.
type B = struct {
x string
y bool
}
type K = struct {
a int
b B
}
type E = interface {
Build([]byte, B) error
Update(dt float64)
Destroy()
}
type T = []map[K]E
دوال الأجناس العامة المدمجة
إلى جانب الأجناس العامة المدمجة للأنواع المركّبة، هناك عدة دوال مدمجة تدعم الأجناس العامة أيضاً. فمثلاً، يمكن استخدام الدالة المدمجة len للحصول على طول قيم المصفوفات والشرائح والخرائط والنصوص والقنوات. وبشكل عام، تُعتبر الدوال الموجودة في الحزمة القياسية unsafe دوال مدمجة أيضاً. وقد قُدّمت دوال الأجناس العامة المدمجة في مقالات سابقة،
الأجناس العامة المخصّصة
منذ الإصدار 1.18، أصبح Go يدعم الأجناس العامة المخصّصة بالفعل. يرجى قراءة كتاب Go Generics 101 لمعرفة كيفية استخدام الأجناس العامة المخصّصة.