مكتبة مغلفة توفر الدعم للدالات المُغلَّفة، وهي الدالات التي تُرفق بنسخة من البيئة التي تُغلفها، أي بنسخة من المتغيرات الخارجية التي تستخدمها الدالة. يمكن للدالات الضمنية الولوج إلى المتغيرات العمومية خارجها، لكن لا يمكنها الولوج إلى المتغيرات المحلية ضمن دالة خارجية تحوي الدالة الضمنية، وذلك لأن الدالة الخارجية قد ينتهي تنفيذها وتُزال متغيراتها من الذاكرة قبل استدعاء الدالة الضمنية. على سبيل المثال:
عرف مد: مؤشر[دالة]؛
دالة جهز_دالة {
عرف ع: صحيح = 10؛
مد = دالة {
طـرفية.اطبع("%d\ج"، ع)؛ // خطأ: في الوقت الذي يُنفذ فيه هذا السطر
// يكون ع قد أزيل من الذاكرة
}؛
}
جهز_دالة()؛
مد()؛
def pf: ptr[func];
func prepareFunc {
def i: Int = 10;
pf = func {
Console.print("%d\n", i); // Error: by the time this line is executed
// i would have been removed from memory
};
}
prepareFunc();
pf();
لتمكين الدالة من الولوج إلى متغيرات الدالة الخارجية تحتاج هذه الدالة أن تُرفق بنسخة من البيانات التي تستخدمها من البيئة الخارجية، وهذا ما توفره الدالات المُغلَّفة التي توفرها مكتبة مغلفة (clsoure)، فهي دالات يغلفها المترجم تلقائيًا بالبيئة الخارجية، أي يُرفقها بنسخة من كل متغير خارجي مؤقت تستخدمه الدالة المغلفة. تعريف الدالة المغلفة يشبه تعريف الدالة الضمنية باستثناء استخدام الكلمة المفتاحية مغلفة (closure) بدل دالة . مثال:
اشمل "مغلفة"؛
عرف مغ: مغلفة ()؛
دالة جهز_مغلفة {
عرف ع: صحيح = 10؛
مغ = مغلفة () {
طـرفية.اطبع("%d\ج"، ع)؛ // صحيح: سيبدل المترجم هذا الولوج تلقائيًا
// إلى ولوج إلى نسخة من ع.
}؛
}
جهز_مغلفة()؛
مغ()؛
import "closure";
def pc: closure ();
func prepareClosure {
def i: Int = 10;
pf = closure () {
Console.print("%d\n", i); // Correct: access to i will be replaced
// by the compiler to an access to a copy of i
};
}
prepareClosure();
pc();
من الملاحظ أعلاه أن كتابة مغلفة دون متن يجعلها تعريفًا لمغلفة، ومن المهم معرفته أن قيد المغلفة يحتوي على مؤشر إلى دالة، بالإضافة إلى سند مشترك إلى قيد البيانات. تبقى المسؤولية على المبرمج لمراعاة عدم الولوج إلى سندات مشتركة تؤدي إلى حلقة مغلقة من السندات المشتركة، وبالتالي تسريب في الذاكرة. على سبيل المثال، إن ولجت من المغلفة إلى سند مشترك وكان هذا السند المشترك يؤشر للقيد الذي يحتوي المغلفة فسينتهي بك الأمر إلى تسرب في الذاكرة (القيد يمسك بالمغلفة والتي بدورها تمسك بالقيد فيبقى كلاهما في الذاكرة دائمًا). في مثل هذه الحالات كل ما تحتاج فعله أن تجعل المغلفة تمسك بسند غير مشترك لتجنب تسريب الذاكرة. مثال:
اشمل "مغلفة"؛
صنف قـيد {
عرف مغ: مغلفة ()؛
عرف ص: صحيح؛
}
عرف ق1: سـندنا[قـيد]؛
ق1.أنشئ()؛
ق1.ص = 10؛
ق1.مغ = مغلفة () {
طـرفية.اطبع("%d\ج"، ق1.ص)؛
}؛
ق1.حرر()؛ // لن يُحرر هنا ق1 ولا المغلفة التي يملكها.
عرف ق2: سـندنا[قـيد]؛
ق2.أنشئ()؛
ق2.ص = 10؛
عرف ق22: سـندهم[قـيد] = ق2؛
ق2.مغ = مغلفة () {
طـرفية.اطبع("%d\ج"، ق22.ص)؛
}}
ق2.حرر()؛ // هنا سيحرر ق2 مع المغلفة التي يملكها.
import "closure";
class Record {
def c: closure ();
def i: Int;
}
def r1: SrdRef[Record];
r1.construct();
r1.i = 10;
r1.c = closure() {
Console.print("%d\n", r1.i);
};
r1.release(); // r1 will not be released and neither will the closure it owns.
def r2: SrdRef[Record];
r2.construct();
r2.i = 10;
def r22: WkRef[Record] = r2;
r2.c = closure() {
Console.print("%d\n", r22.i);
};
r2.release(); // r2 will be released along with the closure it owns.
يمكن للمغلفة أن تستلم قيمًا وترجع قيمًا كما هو الحال مع الدالات:
اشمل "مغلفة"؛
عرف مغ: مغلفة (عائم): صحيح؛
دالة جهز_مغلفة {
عرف ع: صحيح = 10؛
مغ = مغلفة (ن: عائم): صحيح {
أرجع ع * ن؛
}؛
}
جهز_مغلفة()؛
طـرفية.اطبع("%d\ج"، مغ(3.5))؛ // سيطبع 35
import "closure";
def pc: closure (Float): Int;
func prepareClosure {
def i: Int = 10;
pf = closure (j: Float): Int {
return i * j;
};
}
prepareClosure();
Console.print("%d\n", pc(3.5)); // Prints 35
يمكنك تحديد الأسلوب الذي تُلتقط فيه البيانات من محيط الدالة المغلفة، ويمكنك تحديد ذلك لكل متغير على حدة. على سبيل المثال إذا أردت قراءة قيمة من محيد الدالة لكنك لا تحتاج لتغييرها داخل المغلفة فقد يكفيك التقاط نسخة من ذلك المتغير، بينما إذا أردت تغيير قيمة المتغير داخل الدالة المغلفة فستحتاج لالتقاط سند إلى المتغير بدلاً من ذلك. للتحكم بأسلوب التقاط البيانات نتبع الصيغة التالية في تعريف المغلفة:
مغلفة (/*تعريفات_اسلوب_الالتقاط*/)&(/*تعريفات_المدخلات*/): /*صنف_الإرجاع*/ {
// متن المغلفة
}
closure (/*capture_mode_defs*/)&(/*args_defs*/): /*ret_type*/ {
// closure body
}
في المثال التالي نحدد أسلوب الالتقاط للمتغير ن (n) بـ كسند (by_ref) وذلك يخبر يجعل المغلفة تحتفظ بسند للمتغير بدل الاحتفاظ بنسخة، وبالتالي فإن تغير قيمة المتغير داخل المغلفة سيؤدي لتغيير المنغير الأصلي خارج المغلفة.
دالة اختبر_التقط_كسند (ن: صحيح) {
عرف م: مغلفة (ع: صحيح): صحيح؛
م = مغلفة (ن: كسند)&(ع: صحيح): صحيح {
أرجع ع * ن++؛
}
اطبع("ن قبل استدعاء المغلفة: %d\ج"، ن)؛
اطبع("نتيجة المغلفة: %d\ج"، م(3))؛
اطبع("ن بعد استدعاء المغلفة: %d\ج"، ن)؛
}
اختبر_التقط_كسند(5)؛
/*
النتيجة:
ن قبل استدعاء المغلفة: 5
نتيجة المغلفة: 15
ن بعد استدعاء المغلفة: 6
*/
func testCaptureByRef (n: Int) {
def c: closure (i: Int): Int;
c = closure (n: by_ref)&(i: Int): Int {
return i * n++;
};
print("n before calling c: %d\n", n);
print("closure result: %d\n", c(3));
print("n after calling c: %d\n", n);
}
testCaptureByRef(5);
/*
output:
n before calling c: 5
closure result: 15
n after calling c: 6
*/
في هذا المثال لو غيرنا أسلوب التقاط ن من كسند إلى كقيمة لكانت القيمة المطبوعة بعد استعداء المغلفة 5 بدلاً من 6. تتوفر الأنماط التالية لالتقاط البيانات:
يمكن التحقق فيما إن كان مؤشر المغلفة صفرا أم لا باستخدام الدالة أهو_عدم (isNull).
عرف م: مغلفة (ع: صحيح): صحيح؛
م.أهو_عدم() // يرجع 1
م = مغلفة (ع: صحيح): صحيح { أرجع 0؛ }؛
م.أهو_عدم() // يرجع 0
def c: closure (i: Int): Int;
c.isNull() // returns true
c = closure (i: Int): Int { return 0 };
c.isNull() // returns false