البرمجة الوظيفية في Go: من الألف إلى الياء
Go (Golang) هي لغة غالبًا ما ترتبط بالبرمجة الأمرّية والتزامنية. ومع ذلك، فإن نظام الأنواع المرن والدوال من الدرجة الأولى يسمحان بتطبيق الأسلوب الوظيفي بنجاح. في هذه المقالة، سنشرح كيفية استخدام البرمجة الوظيفية (FP) في Go: من المفاهيم الأساسية إلى الأمثلة العملية. ستتعلم كيفية كتابة كود أكثر نظافة وقابلية للتنبؤ والاختبار.
ما هي البرمجة الوظيفية في سياق Go؟
البرمجة الوظيفية هي نموذج حيث تُبنى العمليات الحسابية على تطبيق الدوال، مع تجنب تغيير الحالة والآثار الجانبية. في Go، يتم التعبير عن ذلك من خلال:
- الدوال النقية — تعتمد النتيجة فقط على الوسائط، ولا يوجد تغيير للبيانات الخارجية.
- دوال الترتيب الأعلى — دوال تقبل دوال أخرى كوسائط أو تعيدها.
- الإغلاقات — دوال مجهولة "تتذكر" المتغيرات من النطاق الخارجي.
- ثبات البيانات — تفضيل النسخ بدلاً من التعديل.
على الرغم من أن Go ليست لغة وظيفية بحتة (لا تحتوي على monads أو أنواع بيانات جبرية)، إلا أنها توفر أدوات كافية لاستخدام مناهج البرمجة الوظيفية بشكل فعال.
الدوال المجهولة والإغلاقات
في Go، الدوال هي كائنات من الدرجة الأولى. يمكن تعيينها للمتغيرات، وتمريرها إلى دوال أخرى، وإعادتها. الدوال المجهولة (lambdas) هي أساس الأسلوب الوظيفي.
مثال على دالة مجهولة
package main
import "fmt"
func main() { // دالة مجهولة، تم تعيينها لمتغير multiply := func(x, y int) int { return x * y }
result := multiply(5, 3) fmt.Println(result) // 15}الإغلاقات (Closures)
الإغلاق هو دالة مجهولة "تلتقط" المتغيرات من النطاق الخارجي. هذه أداة قوية لإنشاء المولدات والدوال ذات الحالة.
package main
import "fmt"
// مولد تسلسلfunc sequence() func() int { i := 0 return func() int { i++ return i }}
func main() { next := sequence() fmt.Println(next()) // 1 fmt.Println(next()) // 2 fmt.Println(next()) // 3
// كل استدعاء لـ sequence() ينشئ إغلاقًا جديدًا next2 := sequence() fmt.Println(next2()) // 1}الإغلاقات مثالية لتغليف الحالة دون استخدام المتغيرات العامة — وهذا هو أساس المنهج الوظيفي في Go.
الدوال النقية والثبات
الدالة النقية ليس لها آثار جانبية ولا تغير البيانات المرسلة. في Go، يتطلب هذا نهجًا واعيًا، حيث يتم تمرير الشرائح (slices) والخرائط (maps) بالمرجع.
مثال على دالة نقية (بدون تغيير)
package main
import "fmt"
// دالة نقية: لا تغير الشريحة الأصليةfunc doubleValues(input []int) []int { result := make([]int, len(input)) for i, v := range input { result[i] = v * 2 } return result}
func main() { original := []int{1, 2, 3, 4} doubled := doubleValues(original)
fmt.Println("Original:", original) // [1 2 3 4] fmt.Println("Doubled:", doubled) // [2 4 6 8]}لاحظ: نقوم بإنشاء شريحة جديدة بدلاً من تعديل الشريحة الأصلية. هذا يضمن الثبات (immutability) — مبدأ أساسي في البرمجة الوظيفية.
دوال الترتيب الأعلى: map, filter, reduce
على الرغم من أن Go لا تحتوي على دوال map/filter/reduce مدمجة للشرائح، إلا أنه من السهل تنفيذها بنفسك.
package main
import "fmt"
// Map — تطبق دالة على كل عنصرfunc Map[T any](items []T, fn func(T) T) []T { result := make([]T, len(items)) for