فرز الهياكل في Go: كيف تكتب باختصار ولا تكسر عقلك
\n\nتخيل: لديك شريحة من الهياكل، وتحتاج إلى فرزها حسب أي حقل. الاسم، العمر، الراتب، تاريخ الميلاد — أي شيء. على Stack Overflow السؤال «What is the shortest way to simply sort an array of structs by (arbitrary) field names?» حصل على آلاف المشاهدات. وهذا ليس من قبيل الصدفة. في Go لا توجد طريقة «سحرية» مدمجة مثل list.sort(key=lambda x: x.field) في Python. لكن هناك طريقة أنيقة وقوية — sort.Slice. في هذه المقالة لن نعرض الكود فقط، بل سننظر تحت الغطاء: كيف يعمل الفرز، ماذا يفعل وقت التشغيل وكيف تتجنب المشاكل النموذجية.
المشكلة: «لماذا لا يمكنك ببساطة كتابة sort() في Go؟»
\n\nإذا كنت قادماً من Python أو JavaScript أو Ruby، فأنت معتاد على سطر واحد:
\nusers.sort(key=lambda u: u.age)\nفي Go لا يوجد هذا الرفاهية. الكتابة القوية وغياب الأدوية العامة (حتى Go 1.18) أجبرتك على كتابة تطبيق منفصل لـ sort.Interface لكل نوع هيكل. كان هذا مرهقاً. على سبيل المثال:
type ByAge []User\nfunc (a ByAge) Len() int { return len(a) }\nfunc (a ByAge) Swap(i, j int) { a[i], a[j] = a[j], a[i] }\nfunc (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }\n\nfunc main() {\n users := []User{{"Alice", 30}, {"Bob", 25}}\n sort.Sort(ByAge(users))\n}\nيعمل، لكن لكل حقل — نوع جديد. وإذا كان لديك 10 حقول؟ يتحول الكود إلى ورقة طويلة. تريد شيئاً أكثر إحكاما.
\n\nالحل: sort.Slice — مجموعتك الأنيقة
\n\nبدءاً من Go 1.8، ظهرت في حزمة sort دالة Slice. تأخذ شريحة ودالة مجهولة (إغلاق) تحدد الترتيب. ها هي — أقصر طريقة:
sort.Slice(users, func(i, j int) bool {\n return users[i].Age < users[j].Age\n})\nكل شيء. ثلاثة أسطر. بدون تعريف أنواع جديدة، بدون دوال إضافية. تريد الفرز حسب الاسم؟ نغير الحقل:
\nsort.Slice(users, func(i, j int) bool {\n return users[i].Name < users[j].Name\n})\nوإذا كنت بحاجة إلى ترتيب تنازلي؟ ببساطة نغير الإشارة:
\nsort.Slice(users, func(i, j int) bool {\n return users[i].Age > users[j].Age // الآن تنازلياً\n})\nجميل. لكن كيف يعمل هذا؟ وما هي الخدعة؟
\n\nتحت الغطاء: sort.Slice والدوال المجهولة
\n\nدعنا نحلل ما يحدث عندما تستدعي sort.Slice. الدالة معرفة هكذا:
func Slice(slice interface{}, less func(i, j int) bool)\nأول ما يلفت الانتباه هو interface{}. الدالة تقبل أي شريحة. كيف تعرف طولها وكيف تعيد ترتيب العناصر؟ الإجابة — عبر reflection (حزمة reflect).
داخل sort.Slice تقوم بما يلي:
- \n
- تتحقق من أن ما تم تمريره هو شريحة. إذا مررت شيئاً ليس شريحة (مثل مصفوفة أو هيكل)، سيحدث ذعر (panic) في وقت التنفيذ. \n
- تستخرج الطول عبر
reflect.ValueOf(slice).Len(). \n - تنشئ كائنًا مساعدًا ينفذ الواجهة
sort.Interfaceديناميكياً. هذا الكائن يخزن مرجعاً للشريحة الأصلية (عبر reflection) ودالتكless. \n - تشغل خوارزمية الفرز القياسية (quicksort من حزمة
sort). عندما تحتاج الخوارزمية لمقارنة عنصرين، تستدعي دالتكless. عندما تحتاج لتبديل الأماكن — تستخدمreflect.Swapper(slice). \n
النقطة الأساسية: دالتك less تلتقط الشريحة الأصلية من السياق الخارجي. هذا إغلاق (closure). المترجم يضع المتغير users في الكومة (heap) ليكون متاحاً حتى بعد الخروج من الدالة m