1 انامعلوم فنکشن کی بنیادیں
1.1 نظریاتی تعارف انامعلوم فنکشن کا
انامعلوم فنکشن وہ فنکشن ہیں جن کا کوئی واضح نام نہیں ہوتا۔ انہیں صریح طور پر تعین کیا جا سکتا ہے اور استعمال کیا جا سکتا ہے وہاں جہاں ایک فنکشن کی قسم کی ضرورت ہوتی ہے۔ ان فنکشنز کو عام طور پر مقامی محفوظیت یا مختصر عمر کی صورت میں پیش کیا جاتا ہے۔ نام دار فنکشنز کے مقابلے میں، انامعلوم فنکشنز کو کسی نام کی ضرورت نہیں ہوتی، جس کا مطلب ہے کہ انہیں کسی متغیر میں تعین کیا جا سکتا ہے یا ایک اظہار میں سیدھے طور پر استعمال کیا جا سکتا ہے۔
1.2 انامعلوم فنکشنز کی تعریف اور استعمال
Go زبان میں انامعلوم فنکشنز کی بنیادی ترتیب مندرجہ ذیل ہے:
func(arguments) {
// فنکشن بادی
}
انامعلوم فنکشنز کا استعمال دو صورتوں میں کیا جا سکتا ہے: متغیر میں تعین یا سیدھے تنفیذ۔
- متغیر میں تعین:
sum := func(a int, b int) int {
return a + b
}
result := sum(3, 4)
fmt.Println(result) // Output: 7
اس مثال میں انامعلوم فنکشن کو متغیر sum
میں تعین کیا گیا ہے، اور پھر ہم sum
کو عام فنکشن کی طرح بلایا گیا ہے۔
- سیدھے تنفیذ (جو خود نفس انامعلوم فنکشن کے طور پر بھی جانا جاتا ہے):
func(a int, b int) {
fmt.Println(a + b)
}(3, 4) // Output: 7
اس مثال میں، انامعلوم فنکشن تعین ہونے کے بعد فوراً نفس انامعلوم فنکشن کو تنفیذ کیا جاتا ہے، بغیر کسی متغیر میں تعین کیے۔
1.3 انامعلوم فنکشن کے استعمال کی عملی مثالیں
انامعلوم فنکشنز Go زبان میں وسیع استعمال ہوتے ہیں، اور یہاں کچھ عمومی سناریوز ہیں:
- کال بیک فنکشن کی طرح: انامعلوم فنکشنز کو عموماً کال بیک منطق کی پیادگی کرنے کے لئے استعمال کیا جاتا ہے۔ مثلاً، جب ایک فنکشن کو دوسرے فنکشن کے طور پر متغیر کے طور پر لیا جاتا ہے، تو آپ ایک انامعلوم فنکشن کو منتقل کر سکتے ہیں۔
func traverse(numbers []int, callback func(int)) {
for _, num := range numbers {
callback(num)
}
}
traverse([]int{1, 2, 3}, func(n int) {
fmt.Println(n * n)
})
اس مثال میں، انامعلوم فنکشن کو traverse
کے کال بیک پیرامیٹر کے طور پر منتقل کیا گیا ہے، اور ہر نمبر کو مربع کرکے چھاپا گیا ہے۔
- فوری تنفیذ کے کاموں کیلئے: کبھی کبھی ہمیں صرف ایک بار فنکشن کو تنفیذ کیا جانا چاہئے اور تنفیذی نقطہ قریب ہو۔ انامعلوم فنکشنز تکمیلی کاموں کو فوراً کرنے کے لئے تنفیذ کرنے کے لئے استعمال کیا جا سکتے ہیں اور کوڈ کی بے ضرورتی کمی کو کم کر سکتے ہیں۔
func main() {
// ...دیگر کوڈ...
// فوراً تنفیذ ہونے والا کوڈ بلاک
func() {
// تنفیذی کام کے لئے کوڈ
fmt.Println("فوری انامعلوم فنکشن تنفیذ ہوا۔")
}()
}
یہاں، انامعلوم فنکشن فوراً تعین کرنے کے بعد فوراً تنفیذ کیا جاتا ہے، بغیر کسی بیرونی فنکشن کی تعریف کیے بغیر۔
- قریبی جگہ کی بنیاد پر: انامعلوم فنکشنز کو عموماً کلوجس بنانے کے لئے استعمال کیا جاتا ہے کیونکہ یہ بیرونی متغیرات کو کیپچر کر سکتے ہیں۔
func sequenceGenerator() func() int {
i := 0
return func() int {
i++
return i
}
}
اس مثال میں، sequenceGenerator
ایک انامعلوم فنکشن واپس کرتا ہے جو متغیر i
کو بند کرتا ہے، اور ہر کال i
کو بڑھا دیتا ہے۔
یہ واضح ہے کہ انامعلوم فنکشن کی لچک کا اصل براہ راست کوڈ کرنے میں اہم کردار ہوتا ہے، کوڈ کو سادہ بنانے اور قابل پڑھائی بنانے کے لئے۔ آئندہ حصوں میں، ہم بنیادی طور پر کلوجس کو تفصیل سے پیش کریں گے، جس میں ان کی خصوصیات اور اطلاقات شامل ہوں گے۔
2 کلوجس کی گہرائی میں اظہار کا سمجھ
2.1 کلوجس کا تصور
کلوجس ایک فنکشن ویلیو ہے جو اس کے فنکشن بادی کے باہر کی متغیرات کو حوالہ دیتا ہے۔ یہ فنکشن ان متغیرات کو دستیاب کر سکتا ہے اور ان کو باندھ سکتا ہے، جس کا مطلب ہے کہ یہ نہ صرف ان متغیرات کو استعمال کر سکتا ہے بلکہ ان کو ترتیب دے سکتا ہے۔ کلوجس عام طور پر انامعلوم فنکشنز کے ساتھ منسلک ہوتے ہیں، کیونکہ انامعلوم فنکشنز کا اپنا کوئی نام نہیں ہوتا اور یہ عموماً اسی مقام پر تعین کئے جاتے ہیں، جو کلوجس کیلئے ایسا ماحول پیدا کرتا ہے۔
کلوجس کا تصور اجراءی ماحول اور دائرہ کار سے الگ نہیں کیا جا سکتا ہے۔ Go زبان میں ہر فنکشن کا الگ سٹیک فریم ہوتا ہے، جو فنکشن کے مقامی متغیرات کو ذخیرہ کرتا ہے۔ تاہم، جب فنکشن واپس کرتا ہے، تو اس کا سٹیک فریم موجود نہیں رہتا۔ کلوجس کا جادو اس بات میں ہے کہ بعد ازاں یعنی بیرونی فنکشن واپس کر گیا ہو، کلوجس پھر بھی بیرونی فنکشن کے متغیرات کا حوالہ دے سکتا ہے۔
func outer() func() int {
count := 0
return func() int {
count += 1
return count
}
}
func main() {
closure := outer()
println(closure()) // Output: 1
println(closure()) // Output: 2
}
اس مثال میں، outer
فنکشن ایک کلوجس واپس کرتا ہے جو متغیر count
کا حوالہ دیتا ہے۔ حتیٰ outer
فنکشن کے تنفیذ ہونے کے بعد بھی، کلوجس متغیر count
کو مسلسل تعین کر سکتا ہے۔
2.2 غیر نامی تفاعل کے ساتھ تعلق
غیر نامی تفاعل اور بندشیں متعلق ہوتی ہیں۔ Go زبان میں، غیر نامی تفاعل ایک ایسا تفاعل ہے جو بغیر کسی نام کے ہوتا ہے اور جو جب بھی ضرورت ہو تعین کیا اور براہِ راست استعمال کیا جا سکتا ہے۔ اس قسم کے تفاعل بندش کے عمل کو نافذ کرنے کے لئے بہت مناسب ہوتے ہیں۔
عموماً بندشیں غیر نامی تفاعل کے اندر تعین کی جاتی ہیں جو اپنے آس پاس کے متغیرات کو یاد رکھ سکتی ہیں۔ جب ایک غیر نامی تفاعل اپنے آس پاس کے متغیرات کا حوالہ دیتا ہے، تو غیر نامی تفاعل کے ساتھ حوالے میں لیے گئے متغیرات کا ایک بندش بن جاتا ہے۔
func main() {
adder := func(sum int) func(int) int {
return func(x int) int {
sum += x
return sum
}
}
sumFunc := adder()
println(sumFunc(2)) // Output: 2
println(sumFunc(3)) // Output: 5
println(sumFunc(4)) // Output: 9
}
یہاں، تفاعل adder
ایک غیر نامی تفاعل کو واپس کرتی ہے، جو متغیر sum
کا حوالہ دیتا ہوا ایک بندش بناتا ہے۔
2.3 بندشوں کی خصوصیات
بندشوں کی سب سے واضح خصوصیت یہ ہے کہ وہ یاد رکھنے کی صلاحیت رکھتے ہیں جس ماحول میں یہ بنائی گئی تھیں۔ وہ اپنے اپنے تفاعل کے باہر تعین شدہ متغیرات تک رسائی حاصل کر سکتی ہیں۔ بندشوں کی کھلنے کی فطرت انہیں ممکن بناتی ہے کہ وہ حالت (بیرونی متغیرات کا حوالہ دیتے ہوئے) کو محصور کریں، جو پروگرامنگ میں دیگر زبردست خصوصیات مثلاً ڈیکوریٹرز، حالت کو محصور کرنا، اور سستے ارزاں کی پیشہ ورانہ خصوصیات کے نافذ کرنے کا بنیاد فراہم کرتی ہے۔
حالت کو محصور کرنے کے علاوہ، بندشوں کی منفرد خصوصیات مندرجہ ذیل ہیں:
- متغیرات کی عمر کو دراز کرنا: بندشوں کے حوالہ سے کردار ادا کرنے والے بیرونی متغیرات کی زندگی کے عرصے کو بندش کی وجود کے پورے دوران تک بڑھا دیتا ہے۔
- نجی متغیرات کو محصور کرنا: دوسرے طریقوں سے بندشوں کے داخلی متغیرات کا بائیں دسترس نہیں ہو سکتا، جو نجی متغیرات کو محصور کرنے کا ذریعہ فراہم کرتا ہے۔
2.4 عام پیٹ فیالز اور غور کرنے کے اصول
بندشوں کا استعمال کرتے وقت، کچھ عام پیٹ فیالز اور تفصیلات کے غور کرنے کے اصولات ہیں:
- لوپ متغیر کی بائنڈنگ کی مسئلہ: لوپ کے اندر لوپ کو بنانے کے لئے ایک بندش کا مستقبل میں تشکیل دینا مسئلہ پیدا کر سکتا ہے کیونکہ ہر ایک تکرار کے ساتھ تکرار کے متغیر کا پتا نہیں بدلتا۔
for i := 0; i < 3; i++ {
defer func() {
println(i)
}()
}
// متوقع نتیجہ 0، 1، 2 نہیں ہو سکتا ہے بلکہ 3، 3، 3 ہو سکتا ہے۔
اس مسئلہ سے نجات کے لئے، تکرار کے متغیر کو بندش کے پیرامیٹر کے طور پر پاس کیا جانا چاہئے:
for i := 0; i < 3; i++ {
defer func(i int) {
println(i)
}(i)
}
// درست نتیجہ: 0، 1، 2
-
بندش کی یادگاری کا نقص: اگر ایک بندش کا ایک بڑے مقامی متغیر کا حوالہ ہو اور یہ بندش بہت دیر تک قائم رہے، تو مقامی متغیر واپس نہیں لیا جائے گا، جس سے یادگاری کا نقص ہو سکتا ہے۔
-
بندشوں کے ساتھ متقابل پریشانیاں: اگر ایک بندش کو متقابل طور پر سرگرم کیا جائے اور اس میں کسی مخصوص متغیر کا حوالہ کیا جائے تو یہ ضروری ہے کہ یہ حوالہ متوافقت سے محفوظ ہو۔ عام طور پر، اسے یقینی بنانے کے لئے سنکرونائزیشن آلات جیسے میوٹیکس لاک کی ضرورت ہوتی ہے۔
ان پیٹ فیالز اور غور کرنے کے اصولات کے سمجھ سے ڈوبیز کارکن کو بندشوں کا محفوظ اور مؤثر استعمال کرنے میں مدد ملتی ہے۔