مكتبة lo في Go: أدوات جينيريك مساعدة (Lodash للغة Go)
في عالم تطوير Go، غالباً ما تظهر الحاجة إلى دوال مريحة للتعامل مع المجموعات (Collections) مثل map و filter و reduce الموجودة في لغات أخرى مثل JavaScript أو Python. توفر المكتبة القياسية لـ Go مجموعة أدوات محدودة، مما يجعل كتابة حلقات for المتكررة لكل عملية أمراً مملاً. هنا يأتي دور مكتبة lo - وهي مجموعة قوية من الأدوات المساعدة (Utilities) المبنية على الجينيريك، والمستوحاة من مكتبة Lodash الشهيرة في JavaScript.
تستخدم مكتبة lo الجينيريك (الأنواع العامة) التي ظهرت في Go 1.18+ لتوفير دوال آمنة من حيث النوع (Type-safe) للتعامل مع الشرائح (Slices)، الخرائط (Maps)، القنوات (Channels)، وهياكل البيانات الأخرى. بدلاً من كتابة for i := range slice { ... } لكل عملية تحويل، يمكنك استخدام سطر واحد من الكود: lo.Map(slice, transformFunc). هذا يجعل الكود أكثر قابلية للقراءة، وأكثر إيجازاً، وأقل عرضة للأخطاء.
لماذا تستخدم lo؟ أولاً، لتوفير الوقت: ستتوقف عن كتابة الكود المتكرر (Boilerplate). ثانياً، لزيادة الموثوقية: دوال المكتبة تم اختبارها بدقة وتغطي العديد من الحالات الحدودية (Edge cases). ثالثاً، للتوحيد القياسي: يستخدم فريق العمل بأكمله نفس الدوال بأسماء واضحة، مما يبسط عملية مراجعة الكود. lo هي أداة لا غنى عنها لأي مشروع Go يتعامل بكثافة مع مجموعات البيانات.
التثبيت
يتم تثبيت مكتبة lo بالطريقة القياسية في Go عبر الأمر go get. تأكد من أن لديك إصدار Go 1.18 أو أحدث (لأن المكتبة تستخدم الجينيريك).
go get github.com/samber/lo@latest
بعد تنفيذ هذا الأمر، ستتم إضافة المكتبة إلى ملف go.mod الخاص بك. يمكنك الآن استيرادها في الكود الخاص بك:
import "github.com/samber/lo"
بداية سريعة
لنكتب برنامجاً بسيطاً يستخدم lo لتصفية وتحويل شريحة من الأرقام.
package main
import (
"fmt"
"github.com/samber/lo"
)
func main() {
// الشريحة الأصلية من الأرقام
numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
// تصفية الأرقام الزوجية فقط
evenNumbers := lo.Filter(numbers, func(x int, _ int) bool {
return x%2 == 0
})
fmt.Println("الأرقام الزوجية:", evenNumbers) // [2 4 6 8 10]
// مضاعفة كل رقم
doubled := lo.Map(evenNumbers, func(x int, _ int) int {
return x * 2
})
fmt.Println("الأرقام الزوجية المضاعفة:", doubled) // [4 8 12 16 20]
// جمع كل الأرقام
sum := lo.Sum(doubled)
fmt.Println("المجموع:", sum) // 60
}
كما ترى، أصبح الكود أكثر وضوحاً وتصريحاً (Declarative). لقد تجنبنا كتابة ثلاث حلقات for منفصلة.
الدوال والطرق الأساسية
فيما يلي قائمة بالدوال الرئيسية في مكتبة lo مع أمثلة على الاستخدام. معظم الدوال تأخذ شريحة (Slice) ودالة رد اتصال (Callback).
1. lo.Filter
التوقيع: func Filter[T any](collection []T, predicate func(item T, index int) bool) []T
الوظيفة: تنشئ شريحة جديدة تحتوي فقط على العناصر التي أعادت دالة المسند (Predicate) لها القيمة true من الشريحة الأصلية.
package main
import (
"fmt"
"github.com/samber/lo"
)
func main() {
names := []string{"أحمد", "سارة", "خالد", "نورة", "علي"}
// الاحتفاظ فقط بالأسماء التي يزيد طولها عن 3 أحرف
longNames := lo.Filter(names, func(name string, _ int) bool {
return len(name) > 3
})
fmt.Println(longNames) // [أحمد سارة خالد نورة]
}
2. lo.Map
التوقيع: func Map[T any, R any](collection []T, iteratee func(item T, index int) R) []R
الوظيفة: تحول كل عنصر في الشريحة إلى عنصر جديد من نوع آخر (أو نفس النوع) باستخدام دالة التحويل.
package main
import (
"fmt"
"github.com/samber/lo"
)
func main() {
numbers := []int{1, 2, 3, 4, 5}
// تحويل الأرقام إلى نصوص مع إضافة بادئة
numberStrings := lo.Map(numbers, func(x int, _ int) string {
return fmt.Sprintf("رقم: %d", x)
})
fmt.Println(numberStrings) // [رقم: 1 رقم: 2 رقم: 3 رقم: 4 رقم: 5]
}
3. lo.Reduce
التوقيع: func Reduce[T any, R any](collection []T, accumulator func(agg R, item T, index int) R, initial R) R
الوظيفة: تختزل (تدمج) عناصر الشريحة في قيمة واحدة عن طريق تطبيق دالة تراكمية. مثالية لحساب المجاميع أو بناء الكائنات.
package main
import (
"fmt"
"github.com/samber/lo"
)
func main() {
numbers := []int{10, 20, 30}
// حساب حاصل ضرب جميع الأرقام
product := lo.Reduce(numbers, func(agg int, item int, _ int) int {
return agg * item
}, 1) // القيمة الابتدائية هي 1
fmt.Println("حاصل الضرب:", product) // 6000
}
4. lo.Find
التوقيع: func Find[T any](collection []T, predicate func(item T, index int) bool) (T, bool)
الوظيفة: تبحث عن أول عنصر في الشريحة يحقق الشرط المحدد في دالة المسند. تعيد العنصر وقيمة منطقية تشير إلى نجاح البحث.
package main
import (
"fmt"
"github.com/samber/lo"
)
func main() {
ages := []int{25, 30, 18, 22, 35}
// البحث عن أول عمر أكبر من 28
foundAge, ok := lo.Find(ages, func(age int, _ int) bool {
return age > 28
})
if ok {
fmt.Println("تم العثور على العمر:", foundAge) // 30
} else {
fmt.Println("لم يتم العثور على عمر يحقق الشرط")
}
}
5. lo.Uniq
التوقيع: func Uniq[T comparable](collection []T) []T
الوظيفة: تزيل العناصر المكررة من الشريحة وتحافظ على ترتيب الظهور الأول.
package main
import (
"fmt"
"github.com/samber/lo"
)
func main() {
items := []string{"تفاح", "موز", "تفاح", "برتقال", "موز", "عنب"}
uniqueItems := lo.Uniq(items)
fmt.Println("العناصر الفريدة:", uniqueItems) // [تفاح موز برتقال عنب]
}
6. lo.Chunk
التوقيع: func Chunk[T any](collection []T, size int) [][]T
الوظيفة: تقسم الشريحة إلى أجزاء (شرائح فرعية) متساوية الحجم قدر الإمكان.
package main
import (
"fmt"
"github.com/samber/lo"
)
func main() {
numbers := []int{1, 2, 3, 4, 5, 6, 7}
// تقسيم إلى أجزاء كل جزء يحتوي على 3 عناصر
chunks := lo.Chunk(numbers, 3)
fmt.Println("الأجزاء:", chunks) // [[1 2 3] [4 5 6] [7]]
}
7. lo.Flatten
التوقيع: func Flatten[T any](collection [][]T) []T
الوظيفة: تسوية (تسطيح) شريحة متداخلة من الشرائح إلى شريحة واحدة مستوية.
package main
import (
"fmt"
"github.com/samber/lo"
)
func main() {
nested := [][]int{{1, 2}, {3, 4, 5}, {6}}
flat := lo.Flatten(nested)
fmt.Println("الشريحة المسطحة:", flat) // [1 2 3 4 5 6]
}
8. lo.Sum
التوقيع: func Sum[T constraints.Integer | constraints.Float](collection []T) T
الوظيفة: تحسب مجموع جميع العناصر الرقمية في الشريحة.
package main
import (
"fmt"
"github.com/samber/lo"
)
func main() {
prices := []float64{19.99, 5.50, 3.75, 12.00}
total := lo.Sum(prices)
fmt.Printf("المجموع الكلي: $%.2f\n", total) // $41.24
}
أمثلة متقدمة
لنرى كيف يمكن دمج دوال lo معاً لحل مشاكل أكثر تعقيداً.
مثال: معالجة بيانات المستخدمين
لنفترض أن لدينا شريحة من المستخدمين ونريد الحصول على أسماء المستخدمين النشطين الذين تزيد أعمارهم عن 18 عاماً، مع ترتيبهم أبجدياً.
package main
import (
"fmt"
"sort"
"github.com/samber/lo"
)
type User struct {
Name string
Age int
Active bool
}
func main() {
users := []User{
{"أحمد", 25, true},
{"سارة", 17, true},
{"خالد", 30, false},
{"نورة", 22, true},
{"علي", 19, true},
}
// 1. تصفية المستخدمين النشطين فقط
activeUsers := lo.Filter(users, func(u User, _ int) bool {
return u.Active
})
// 2. تصفية المستخدمين الذين تزيد أعمارهم عن 18
adultActiveUsers := lo.Filter(activeUsers, func(u User, _ int) bool {
return u.Age > 18
})
// 3. استخراج الأسماء فقط
names := lo.Map(adultActiveUsers, func(u User, _ int) string {
return u.Name
})
// 4. ترتيب الأسماء أبجدياً
sort.Strings(names)
fmt.Println("المستخدمون النشطون البالغون:", names)
// المخرجات: [أحمد علي نورة]
}
الخلاصة
مكتبة lo هي إضافة قوية لأي مطور Go. إنها توفر مجموعة واسعة من الدوال المساعدة التي تجعل التعامل مع المجموعات أسهل وأسرع وأقل عرضة للأخطاء. باستخدام الجينيريك، تقدم المكتبة أداءً عالياً وأماناً من حيث النوع. سواء كنت تعمل على مشروع صغير أو تطبيق كبير، فإن lo ستساعدك على كتابة كود أنظف وأكثر فعالية. ابدأ باستخدامها اليوم واستمتع بتجربة برمجة أفضل في Go!