الإغلاق (برمجة الحاسوب)
في لغات البرمجة ، تُعرف الدالة المغلقة ، أو الدالة المغلقة المعجمية ، بأنها أسلوب لتنفيذ ربط الأسماء ضمن نطاق معجمي في لغة ذات دوال من الدرجة الأولى . عمليًا ، الدالة المغلقة هي سجل يخزن دالة [ أ ] مع بيئة. [ 1 ] البيئة عبارة عن ربط يربط كل متغير حر في الدالة (المتغيرات المستخدمة محليًا، ولكنها مُعرّفة في نطاق مُحيط) بالقيمة أو المرجع الذي رُبط به الاسم عند إنشاء الدالة المغلقة. [ ب ] على عكس الدالة العادية، تسمح الدالة المغلقة للدالة بالوصول إلى تلك المتغيرات المُخزّنة من خلال نسخ الدالة المغلقة لقيمها أو مراجعها، حتى عند استدعاء الدالة خارج نطاقها.
التاريخ وأصل الكلمات
تم تطوير مفهوم الإغلاقات في الستينيات من القرن الماضي للتقييم الميكانيكي للتعبيرات في حساب لامدا ، وتم تنفيذه بالكامل لأول مرة في عام 1970 كميزة لغوية في لغة البرمجة PAL لدعم الدوال من الدرجة الأولى ذات النطاق المعجمي . [ 2 ]
عرّف بيتر لاندين مصطلح "الإغلاق" عام 1964 بأنه يتألف من جزء بيئي وجزء تحكم ، كما هو مستخدم في جهازه SECD لتقييم التعبيرات. [ 3 ] وينسب جويل موسى إلى لاندين الفضل في تقديم مصطلح "الإغلاق" للإشارة إلى تعبير لامدا ذي روابط مفتوحة (متغيرات حرة) تم إغلاقها (أو ربطها) بواسطة البيئة المعجمية، مما ينتج عنه تعبير مغلق ، أو إغلاق. [ 4 ] [ 5 ] وقد تبنى سوسمان وستيل هذا الاستخدام لاحقًا عندما عرّفا لغة Scheme عام 1975، [ 6 ] وهي نسخة معجمية من لغة Lisp ، وأصبح هذا الاستخدام شائعًا .
استخدم سوسمان وأبيلسون مصطلح "الإغلاق" في ثمانينيات القرن العشرين بمعنى ثانٍ غير ذي صلة: خاصية عامل يُضيف بيانات إلى بنية بيانات ، بحيث يُمكنه أيضًا إضافة بنى بيانات متداخلة. هذا الاستخدام للمصطلح مُستمد من تطبيقاته في الرياضيات ، وليس من استخدامه السابق في علوم الحاسوب. ويرى المؤلفان أن هذا التداخل في المصطلحات "مؤسف". [ 7 ]
الوظائف المجهولة
غالبًا ما يُستخدم مصطلح الإغلاق كمرادف للدالة المجهولة ، على الرغم من أن الدالة المجهولة هي دالة حرفية بدون اسم، بينما الإغلاق هو حالة من الدالة، قيمة ، تم ربط متغيراتها غير المحلية إما بالقيم أو بمواقع التخزين (اعتمادًا على اللغة؛ انظر قسم البيئة المعجمية أدناه).
على سبيل المثال، في كود بايثون التالي :
def f ( x : int ) -> Callable [[ int ], int ]: def g ( y : int ) -> int : return x + y return g # إرجاع دالة مغلقة.def h ( x : int ) -> Callable [[ int ], int ]: return lambda y : x + y # إرجاع دالة مغلقة.# إسناد دوال إغلاق محددة إلى المتغيرات. أ : دالة قابلة للاستدعاء [[ int ], int ] = f ( 1 ) ب : دالة قابلة للاستدعاء [[ int ], int ] = h ( 1 )# استخدام الدوال المغلقة المخزنة في المتغيرات. تحقق من أن a ( 5 ) == 6 تحقق من أن b ( 5 ) == 6# استخدام الدوال المغلقة دون ربطها بالمتغيرات أولاً. assert f ( 1 )( 5 ) == 6 # f(1) هي الدالة المغلقة. assert h ( 1 )( 5 ) == 6 # h(1) هي الدالة المغلقة.قيمتا aو bهما دالتان مغلقتان، في كلتا الحالتين يتم إنشاؤهما عن طريق إرجاع دالة متداخلة مع متغير حر من الدالة المحيطة، بحيث يرتبط المتغير الحر بقيمة معامل xالدالة المحيطة. الدالتان المغلقتان في aو bمتطابقتان وظيفيًا. الفرق الوحيد في التنفيذ هو أننا في الحالة الأولى استخدمنا دالة متداخلة باسم ، gبينما في الحالة الثانية استخدمنا دالة متداخلة مجهولة (باستخدام الكلمة المفتاحية في بايثون lambdaلإنشاء دالة مجهولة). الاسم الأصلي، إن وُجد، المستخدم في تعريفهما غير ذي صلة.
الدالة المغلقة هي قيمة كأي قيمة أخرى. لا تحتاج إلى إسنادها إلى متغير، بل يمكن استخدامها مباشرةً، كما هو موضح في السطرين الأخيرين من المثال. يُمكن اعتبار هذا الاستخدام "دالة مغلقة مجهولة".
تعريفات الدوال المتداخلة ليست دوالًا مغلقة بحد ذاتها: فهي تحتوي على متغير حر لم يتم ربطه بعد. فقط عند تقييم الدالة المُحيطة بقيمة للمعامل، يتم ربط المتغير الحر للدالة المتداخلة، مما يُنشئ دالة مغلقة، والتي تُعاد بعد ذلك من الدالة المُحيطة.
أخيرًا، لا يُمكن التمييز بين الإغلاق والدالة ذات المتغيرات الحرة إلا عندما يكون خارج نطاق المتغيرات غير المحلية، وإلا فإن بيئة التعريف وبيئة التنفيذ تتطابقان ولا يوجد ما يميز بينهما (لا يُمكن التمييز بين الربط الثابت والديناميكي لأن الأسماء تُشير إلى نفس القيم). على سبيل المثال، في البرنامج أدناه، تُنفَّذ الدوال ذات المتغيرات الحرة x(المرتبطة بالمتغير غير المحلي xذي النطاق العام) في نفس البيئة التي xعُرِّف فيها المتغير، لذا لا يهم ما إذا كانت هذه الدوال إغلاقات فعلية أم لا.
x : int = 1 nums : list [ int ] = [ 1 , 2 , 3 ]دالة f ( y : int ) -> int : تُرجع x + ymap ( f , nums ) map ( lambda y : x + y , nums )يتم تحقيق ذلك في أغلب الأحيان عن طريق إرجاع الدالة، حيث يجب تعريف الدالة ضمن نطاق المتغيرات غير المحلية، وفي هذه الحالة يكون نطاقها الخاص أصغر عادةً.
يمكن تحقيق ذلك أيضًا عن طريق تظليل المتغيرات (الذي يقلل نطاق المتغير غير المحلي )، على الرغم من أن هذا أقل شيوعًا في الممارسة العملية، لأنه أقل فائدة، كما أن التظليل غير مستحب. في هذا المثال، fيمكن اعتبار إغلاقًا لأن xفي جسم fمرتبط بـ xفي النطاق العام، وليس بالنطاق xالمحلي g.
x : int = 0دالة f ( y : int ) -> int : تُرجع x + ydef g ( z : int ) -> int : x : int = 1 # المتغير المحلي x يُخفي المتغير العام x return f ( z )print ( g ( 1 )) # يطبع 1، وليس 2التطبيقات
يرتبط استخدام الدوال المغلقة باللغات التي تُعتبر فيها الدوال كائنات من الدرجة الأولى ، حيث يمكن إرجاع الدوال كنتائج من دوال ذات رتبة أعلى ، أو تمريرها كوسائط لاستدعاءات دوال أخرى؛ فإذا كانت الدوال ذات المتغيرات الحرة من الدرجة الأولى، فإن إرجاع أحدها يُنشئ دالة مغلقة. يشمل ذلك لغات البرمجة الوظيفية مثل Lisp و ML ، والعديد من لغات البرمجة الحديثة متعددة الأنماط، مثل Julia و Python و Rust . كما تُستخدم الدوال المغلقة غالبًا مع دوال الاستدعاء ، لا سيما لمعالجات الأحداث ، كما هو الحال في JavaScript ، حيث تُستخدم للتفاعل مع صفحات الويب الديناميكية .
يمكن استخدام الإغلاقات أيضًا بأسلوب تمرير الاستمرارية لإخفاء الحالة . وبالتالي، يمكن تنفيذ بنيات مثل الكائنات وهياكل التحكم باستخدام الإغلاقات. في بعض اللغات، قد يحدث الإغلاق عندما تُعرَّف دالة داخل دالة أخرى، وتشير الدالة الداخلية إلى متغيرات محلية للدالة الخارجية. عند وقت التشغيل ، عند تنفيذ الدالة الخارجية، يتشكل إغلاق يتكون من كود الدالة الداخلية ومراجع (القيم العليا) لأي متغيرات من الدالة الخارجية التي يتطلبها الإغلاق.
وظائف من الدرجة الأولى
تظهر الدوال المغلقة عادةً في اللغات التي تدعم الدوال من الدرجة الأولى ، أي أن هذه اللغات تُمكّن من تمرير الدوال كوسائط، وإرجاعها من استدعاءات الدوال، وربطها بأسماء المتغيرات، وما إلى ذلك، تمامًا مثل الأنواع الأبسط كالسلاسل النصية والأعداد الصحيحة. على سبيل المثال، انظر إلى دالة Scheme التالية :
; إرجاع قائمة بجميع الكتب التي بيع منها ما لا يقل عن THRESHOLD من النسخ. ( define ( best-selling-books threshold ) ( filter ( lambda ( book ) ( >= ( book-sales book ) threshold )) book-list ))في هذا المثال، يظهر تعبير لامدا(lambda (book) (>= (book-sales book) threshold)) داخل الدالة best-selling-books. عند تقييم تعبير لامدا، يقوم Scheme بإنشاء إغلاق يتكون من رمز تعبير لامدا ومرجع إلى المتغير threshold، وهو متغير حر داخل تعبير لامدا.
ثم تُمرَّر الدالة المغلقة إلى filterالدالة، التي تستدعيها بشكل متكرر لتحديد الكتب التي ستُضاف إلى قائمة النتائج والكتب التي ستُستبعد. ولأن الدالة المغلقة تحتوي على مرجع إلى متغير threshold، فإنها تستطيع استخدام هذا المتغير في كل مرة تستدعيها. قد تُعرَّف filterالدالة في ملف منفصل.filter
إليكم نفس المثال مُعاد كتابته بلغة TypeScript ، وهي لغة شائعة أخرى تدعم الدوال المغلقة:
// إرجاع قائمة بجميع الكتب التي بيع منها ما لا يقل عن "الحد الأدنى" من النسخ. دالة bestSellingBooks ( الحد الأدنى : رقم ) : Book [] { return bookList.filter ( book = > book.sales > = الحد الأدنى ); }يتم استخدام عامل السهم =>لتعريف تعبير دالة السهم ، وطريقة [ 8 ] بدلاً من دالة عامة ، ولكن بخلاف ذلك Array.prototype.filterفإن بنية وتأثير الكود هما نفس الشيء.filter
قد تقوم الدالة بإنشاء إغلاق وإعادته، كما في هذا المثال:
نوع الدالة الأحادية = ( س : عدد ) => عدد ؛// تُعيد هذه الدالة دالة تُقارب مشتقة f // باستخدام فترة dx، والتي يجب أن تكون صغيرة بما يكفي. function derivative ( f : UnaryFunction , dx : number ) : UnaryFunction { return x => ( f ( x + dx ) - f ( x )) / dx ; }لأن الإغلاق في هذه الحالة يستمر بعد انتهاء تنفيذ الدالة التي أنشأته، فإن المتغيرات fتبقى dxموجودة بعد انتهاء الدالة derivative، حتى بعد خروجها من نطاقها وعدم ظهورها. في اللغات التي لا تدعم الإغلاقات، يتزامن عمر المتغير المحلي التلقائي مع انتهاء إطار المكدس الذي تم فيه تعريف هذا المتغير. أما في اللغات التي تدعم الإغلاقات، فيجب أن تستمر المتغيرات في الوجود طالما أن أي إغلاقات موجودة تشير إليها. ويتم تنفيذ ذلك عادةً باستخدام نوع من أنواع جمع البيانات المهملة .
تمثيل الولاية
يمكن استخدام الإغلاق لربط دالة بمجموعة من المتغيرات " الخاصة "، التي تبقى موجودة حتى بعد استدعاء الدالة عدة مرات. يقتصر نطاق المتغير على الدالة المغلقة فقط، لذا لا يمكن الوصول إليه من أجزاء أخرى من البرنامج. تُشابه هذه المتغيرات الخاصة في البرمجة كائنية التوجه ، وفي الواقع، تُشبه الإغلاقات كائنات الدوال ذات الحالة (أو الدوال الوظيفية) التي تحتوي على دالة استدعاء واحدة.
في اللغات ذات الحالة، يمكن استخدام الدوال المغلقة لتطبيق نماذج تمثيل الحالة وإخفاء المعلومات ، نظرًا لأن قيم الدالة المغلقة (المتغيرات المغلقة) ذات مدى غير محدد ، وبالتالي تظل القيمة المُحددة في استدعاء واحد متاحة في الاستدعاء التالي. لا تتمتع الدوال المغلقة المستخدمة بهذه الطريقة بشفافية مرجعية ، وبالتالي لم تعد دوالًا خالصة ؛ ومع ذلك، فهي شائعة الاستخدام في اللغات الوظيفية غير الخالصة مثل Scheme .
استخدامات أخرى
للأغطية استخدامات عديدة:
- نظرًا لأنّ الدوال المغلقة تؤجل التقييم - أي أنها لا تُنفّذ أي شيء حتى يتم استدعاؤها - يُمكن استخدامها لتعريف هياكل التحكم. على سبيل المثال، جميع هياكل التحكم القياسية في لغة سمول توك ، بما في ذلك التفرعات (if/then/else) والحلقات (while و for)، مُعرّفة باستخدام كائنات تقبل توابعها الدوال المغلقة. كما يُمكن للمستخدمين تعريف هياكل التحكم الخاصة بهم بسهولة.
- في اللغات التي تُطبّق عملية الإسناد، يُمكن إنشاء دوال متعددة تعمل على نفس البيئة، مما يُتيح لها التواصل بشكل خاص عن طريق تغيير تلك البيئة. في لغة Scheme:
( define foo #f ) ( define bar #f )( let (( secret-message "none" )) ( set! foo ( lambda ( msg ) ( set! secret-message msg ))) ( set! bar ( lambda () secret-message )))( عرض ( الشريط )) ؛ يطبع "لا شيء" ( سطر جديد ) ( فو "قابلني عند الأرصفة عند منتصف الليل" ) ( عرض ( الشريط )) ؛ يطبع "قابلني عند الأرصفة عند منتصف الليل"ملاحظة: يطلق بعض المتحدثين على أي بنية بيانات تربط بيئة معجمية اسم الإغلاق، لكن المصطلح يشير عادةً إلى الدوال على وجه التحديد.
التطبيق والنظرية
تُنفَّذ الدوال المغلقة عادةً باستخدام بنية بيانات خاصة تحتوي على مؤشر إلى رمز الدالة ، بالإضافة إلى تمثيل للبيئة المعجمية للدالة (أي مجموعة المتغيرات المتاحة) وقت إنشاء الدالة المغلقة. تربط بيئة الإشارة الأسماء غير المحلية بالمتغيرات المقابلة في البيئة المعجمية وقت إنشاء الدالة المغلقة، كما تُمدد عمرها إلى مدة لا تقل عن مدة عمر الدالة المغلقة. عند استدعاء الدالة المغلقة لاحقًا، ربما ببيئة معجمية مختلفة، تُنفَّذ الدالة مع متغيراتها غير المحلية التي تشير إلى تلك التي استُخدمت في الدالة المغلقة، وليس إلى البيئة الحالية.
لا يمكن لتطبيق لغة برمجة أن يدعم الإغلاقات الكاملة بسهولة إذا كان نموذج ذاكرة وقت التشغيل يخصص جميع المتغيرات التلقائية على مكدس خطي . في مثل هذه اللغات، تُحرر المتغيرات المحلية التلقائية للدالة عند انتهاء تنفيذها. مع ذلك، يتطلب الإغلاق أن تبقى المتغيرات الحرة التي يشير إليها بعد انتهاء تنفيذ الدالة المُحيطة. لذا، يجب تخصيص هذه المتغيرات بحيث تبقى حتى زوال الحاجة إليها، عادةً عبر تخصيص الذاكرة الديناميكية (heap allocation )، بدلاً من تخصيصها على المكدس، ويجب إدارة دورة حياتها بحيث تبقى حتى انتهاء استخدام جميع الإغلاقات التي تشير إليها.
هذا يفسر سبب استخدام اللغات التي تدعم الإغلاقات بشكل أصلي لتقنية جمع البيانات المهملة . البدائل هي إدارة الذاكرة يدويًا للمتغيرات غير المحلية (تخصيصها صراحةً في الكومة وتحريرها عند الانتهاء)، أو، في حال استخدام تخصيص المكدس، أن تقبل اللغة أن بعض حالات الاستخدام ستؤدي إلى سلوك غير محدد ، بسبب المؤشرات المعلقة إلى متغيرات تلقائية محررة، كما هو الحال في تعابير لامدا في C++11 [ 10 ] أو الدوال المتداخلة في GNU C. [ 11 ] تُعرف مشكلة الوسيط الوظيفي ( أو مشكلة "الوسيط الوظيفي") بصعوبة تنفيذ الدوال ككائنات من الدرجة الأولى في لغة برمجة تعتمد على المكدس مثل C أو C++. وبالمثل، في D الإصدار 1، يُفترض أن المبرمج يعرف كيفية التعامل مع المندوبين والمتغيرات المحلية التلقائية، حيث ستكون مراجعها غير صالحة بعد العودة من نطاق تعريفها (المتغيرات المحلية التلقائية موجودة في المكدس) - وهذا لا يزال يسمح بالعديد من الأنماط الوظيفية المفيدة، ولكنه يتطلب في الحالات المعقدة تخصيصًا صريحًا للكومة للمتغيرات. حلّت النسخة الثانية من لغة D هذه المشكلة من خلال تحديد المتغيرات التي يجب تخزينها في الذاكرة الديناميكية، وتُجري عملية تخصيص تلقائية. ولأن لغة D تستخدم خاصية جمع البيانات المهملة، فلا حاجة في كلا النسختين لتتبع استخدام المتغيرات عند تمريرها.
في لغات البرمجة الوظيفية الصارمة ذات البيانات غير القابلة للتغيير ( مثل إرلانج )، يسهل جدًا تطبيق إدارة الذاكرة التلقائية (جمع البيانات المهملة)، لعدم وجود دورات محتملة في مراجع المتغيرات. على سبيل المثال، في إرلانج، تُخصص جميع الوسائط والمتغيرات في الذاكرة الديناميكية (heap)، بينما تُخزن مراجعها في الذاكرة المكدسة (stack). بعد انتهاء الدالة، تظل المراجع صالحة. ويتم تنظيف الذاكرة الديناميكية بواسطة جامع البيانات المهملة التزايدي.
في لغة ML، تكون المتغيرات المحلية ذات نطاق معجمي، وبالتالي تحدد نموذجًا يشبه المكدس، ولكن نظرًا لأنها مرتبطة بالقيم وليس بالكائنات، فإن التنفيذ حر في نسخ هذه القيم إلى بنية بيانات الإغلاق بطريقة غير مرئية للمبرمج.
تفتقر لغة Scheme ، التي تتميز بنظام نطاق معجمي مشابه للغة ALGOL مع متغيرات ديناميكية وجمع البيانات المهملة، إلى نموذج برمجة المكدس، وبالتالي لا تعاني من قيود اللغات القائمة على المكدس. تُعبّر الدوال المغلقة (Closures) بشكل طبيعي في Scheme. يُغلّف شكل لامدا (lambda) الكود، وتستمر المتغيرات الحرة لبيئتها داخل البرنامج طالما أمكن الوصول إليها، وبالتالي يمكن استخدامها بحرية تامة كأي تعبير آخر في Scheme.
ترتبط الدوال المغلقة ارتباطًا وثيقًا بالفاعلين في نموذج الفاعل للحوسبة المتزامنة، حيث تُسمى القيم الموجودة في البيئة المعجمية للدالة بالمعارف . ومن المسائل المهمة المتعلقة بالدوال المغلقة في لغات البرمجة المتزامنة ، إمكانية تحديث المتغيرات داخل الدالة المغلقة، وكيفية مزامنة هذه التحديثات إن أمكن. ويُقدم الفاعلون أحد الحلول لهذه المسألة. [ 12 ]
ترتبط الإغلاقات ارتباطًا وثيقًا بكائنات الوظائف ؛ يُعرف التحويل من الأول إلى الأخير باسم إلغاء الوظائف أو رفع لامدا ؛ انظر أيضًا تحويل الإغلاق .
الاختلافات في الدلالات
البيئة المعجمية
بما أن اللغات المختلفة لا تتفق دائمًا على تعريف موحد للبيئة المعجمية، فقد تختلف تعريفاتها للإغلاق أيضًا. يُعرّف التعريف المبسط الشائع للبيئة المعجمية بأنها مجموعة جميع روابط المتغيرات ضمن النطاق، وهذا ما يجب أن تلتقطه الإغلاقات في أي لغة. مع ذلك، يختلف معنى ربط المتغير أيضًا. في اللغات الإجرائية، ترتبط المتغيرات بمواقع نسبية في الذاكرة يمكنها تخزين القيم. على الرغم من أن الموقع النسبي للربط لا يتغير أثناء التشغيل، إلا أن القيمة في الموقع المرتبط قد تتغير. في هذه اللغات، ولأن الإغلاق يلتقط الربط، فإن أي عملية على المتغير، سواء أُجريت من داخل الإغلاق أم لا، تُنفذ على نفس موقع الذاكرة النسبي. يُطلق على هذا غالبًا اسم التقاط المتغير "بالمرجع". إليك مثال يوضح هذا المفهوم في ECMAScript ، وهي إحدى هذه اللغات:
let f : () => number ; let g : () => number ; function foo () { let x : number ; f = () => ++ x ; g = () => -- x ; x = 1 ; console . log ( `inside foo, call to f(): ${ f () } ` ); } foo (); // 2 console . log ( `call to g(): ${ g () } ` ); // 1 (--x) console . log ( `call to g(): ${ g () } ` ); // 0 (--x) console . log ( `call to f(): ${ f () } ` ); // 1 (++x) console . log ( `call to f(): ${ f () } ` ); // 2 (++x)تستخدم الدالة fooوالإغلاقات المشار إليها بواسطة المتغيرات fجميعها gنفس موقع الذاكرة النسبي المشار إليه بواسطة المتغير المحلي x.
في بعض الحالات، قد يكون السلوك المذكور أعلاه غير مرغوب فيه، ومن الضروري ربط إغلاق معجمي مختلف. في لغة ECMAScript، يتم ذلك باستخدام Function.bind().
مثال 1: الإشارة إلى متغير غير مرتبط
var module = { x : 42 , get () { return this . x ; } } var unboundGet = module . get ; console . log ( unboundGet ()); // يتم استدعاء الدالة في النطاق العام // تُصدر قيمة غير مُعرّفة لأن 'x' غير مُحددة في النطاق العام.var boundGet = unboundGet.bind ( module ); // تحديد وحدة الكائن كدالة مغلقة console.log ( boundGet ( ) ); // يُصدر 42مثال 2: الإشارة غير المقصودة إلى متغير مقيد
في هذا المثال، من المتوقع أن يقوم كل رابط بإصدار معرّفه عند النقر عليه؛ ولكن نظرًا لأن المتغير 'e' مرتبط بالنطاق أعلاه، ويتم تقييمه بشكل كسول عند النقر، فإن ما يحدث فعليًا هو أن كل حدث نقر يُصدر معرّف العنصر الأخير في 'elements' المرتبط في نهاية حلقة for . [ 14 ]
var elements = document.getElementsByTagName ( 'a' ); // خطأ: e مرتبط بالدالة التي تحتوي على حلقة 'for' ، وليس بإغلاق " handle " for ( var e of elements ) { e.onclick = function handle ( ) { console.log ( e.id ) ; } }ومرة أخرى هنا، يجب ربط المتغير eبنطاق الكتلة باستخدام handle.bind(this)الكلمة المفتاحية `or` let.
من جهة أخرى، تربط العديد من لغات البرمجة الوظيفية، مثل ML ، المتغيرات مباشرةً بالقيم. في هذه الحالة، ولأنه لا توجد طريقة لتغيير قيمة المتغير بعد ربطه، فلا حاجة لمشاركة الحالة بين الدوال المغلقة، إذ تستخدم جميعها نفس القيم. يُطلق على هذا غالبًا اسم "التقاط المتغير بالقيمة". تندرج فئات Java المحلية والمجهولة أيضًا ضمن هذه الفئة، فهي تتطلب أن تكون المتغيرات المحلية الملتقطة من النوع المحدد final، مما يعني أيضًا عدم الحاجة لمشاركة الحالة.
تتيح بعض لغات البرمجة الاختيار بين تخزين قيمة المتغير أو موقعه. على سبيل المثال، في لغة C++11، تُعرَّف المتغيرات المخزنة إما باستخدام `require` [&]، أي التخزين بالمرجع، أو باستخدام ` [=]require`، أي التخزين بالقيمة.
وهناك فئة فرعية أخرى، وهي لغات البرمجة الوظيفية الكسولة مثل هاسكل ، التي تربط المتغيرات بنتائج العمليات الحسابية المستقبلية بدلاً من القيم. لنأخذ هذا المثال في هاسكل:
-- Haskell foo :: Fractional a => a -> a -> ( a -> a ) foo x y = ( \ z -> z + r ) حيث r = x / yf :: كسري a => a -> a f = foo 1 0main = print ( f 123 )يرتبط الربط الذي rتم التقاطه بواسطة الإغلاق المُعرَّف داخل الدالة fooبالحساب (x / y)- والذي ينتج عنه في هذه الحالة القسمة على صفر. ومع ذلك، بما أن الحساب هو ما يتم التقاطه وليس القيمة، فإن الخطأ لا يظهر إلا عند استدعاء الإغلاق، ثم محاولة استخدام الربط الملتقط.
إغلاق المغادرة
تظهر اختلافات أخرى في سلوك بنيات أخرى ذات نطاق معجمي، مثل عبارات return`require` breakو` continuerequire`. يمكن، بشكل عام، النظر إلى هذه البنيات من منظور استدعاء استمرار الهروب الذي أنشأته عبارة تحكم محيطة (في حالة `require` breakو`require` continue، يتطلب هذا التفسير النظر إلى بنيات التكرار من منظور استدعاءات الدوال المتكررة). في بعض اللغات، مثل ECMAScript، returnيشير `require` إلى الاستمرار الذي أنشأته الدالة المغلقة الأقرب معجميًا إلى عبارة `require` - وبالتالي، فإن `require` returnداخل دالة مغلقة ينقل التحكم إلى الكود الذي استدعاها. مع ذلك، في Smalltalk ، يستدعي المعامل `require`، المشابه ظاهريًا، ^استمرار الهروب الذي تم إنشاؤه لاستدعاء الدالة، متجاهلًا استمرارات الهروب لأي دوال مغلقة متداخلة. لا يمكن استدعاء استمرار الهروب لدالة مغلقة معينة في Smalltalk إلا ضمنيًا بالوصول إلى نهاية كود الدالة المغلقة. تُبرز هذه الأمثلة في ECMAScript وSmalltalk الفرق:
"Smalltalk" foo | xs | xs := #( 1 2 3 4 ) . xs do: [ : x | ^ x ] . ^ 0 bar Transcript show: ( self foo printString ) "prints 1"// دالة ECMAScript foo ( ) : number { let xs : number [ ] = [ 1 , 2 , 3 , 4 ]; xs.forEach ( function ( x ) { return x ; }); return 0 ; } console.log ( foo ( )); // يطبع 0ستختلف سلوكيات مقاطع الشفرة المذكورة أعلاه لأن ^عامل التشغيل في Smalltalk وعامل التشغيل في JavaScript returnليسا متماثلين. في مثال ECMAScript، return x;سيخرج من الإغلاق الداخلي لبدء تكرار جديد للحلقة forEach، بينما في مثال Smalltalk، ^xسينهي الحلقة ويعود من الدالة foo.
توفر لغة Common Lisp بنيةً تُتيح التعبير عن أيٍّ من الإجراءين المذكورين أعلاه: (return-from foo x)تتصرف Lisp كلغة Smalltalk^x ، بينما (return-from nil x)تتصرف Lisp كلغة JavaScriptreturn x . وبالتالي، تُتيح Smalltalk إمكانية استمرار استمرار الهروب المُلتقط لفترة أطول من مدى إمكانية استدعائه بنجاح. انظر إلى المثال التالي:
"Smalltalk" foo ^ [ : x | ^ x ] bar | f | f := self foo . f value: 123 "خطأ!"عند استدعاء الدالة المغلقة التي تُعيدها الطريقة foo، فإنها تحاول إعادة قيمة من استدعاء الدالة التي fooأنشأت هذه الدالة المغلقة. ولأن هذا الاستدعاء قد أعاد قيمة بالفعل، ولأن نموذج استدعاء الدوال في لغة سمول توك لا يتبع مبدأ " مكدس الاستدعاءات المتشابكة" لتسهيل عمليات الإرجاع المتعددة، فإن هذه العملية تُؤدي إلى حدوث خطأ.
تتيح بعض لغات البرمجة، مثل روبي ، للمبرمج اختيار طريقة returnالتقاط البيانات. مثال على ذلك في روبي:
روبي# إغلاق باستخدام إجراء def foo f = Proc.new { return " العودة من foo من داخل الإجراء" } f.call # يغادر التحكم foo هنا return "العودة من foo" end# إغلاق باستخدام دالة لامدا def bar f = lambda { return "العودة من دالة لامدا" } f.call # لا يغادر التحكم bar هنا return "العودة من bar " endputs foo # يطبع "العودة من foo من داخل الإجراء" puts bar # يطبع "العودة من bar"كلاهما Proc.newفي lambdaهذا المثال هما طريقتان لإنشاء إغلاق، لكن دلالات الإغلاقات التي تم إنشاؤها بهذه الطريقة تختلف فيما يتعلق بالعبارة return.
في لغة Scheme ، يكون تعريف ونطاق returnعبارة التحكم واضحين (وقد سُميت "return" بشكل اعتباطي لأغراض المثال فقط). فيما يلي ترجمة مباشرة لمثال Ruby.
; المخطط ( تعريف استدعاء/نسخة إلى استدعاء مع استمرارية حالية )( define ( foo ) ( call/cc ( lambda ( return ) ( define ( f ) ( return "return from foo from inside proc" )) ( f ) ; control leave foo here ( return "return from foo" ))))( define ( bar ) ( call/cc ( lambda ( return ) ( define ( f ) ( call/cc ( lambda ( return ) ( return "return from lambda" )))) ( f ) ; لا يغادر التحكم bar هنا ( return "return from bar" ))))( display ( foo )) ; يطبع "العودة من foo من داخل الإجراء" ( newline ) ( display ( bar )) ; يطبع "العودة من bar"بنيات شبيهة بالإغلاق
تحتوي بعض اللغات على خصائص تحاكي سلوك الدوال المغلقة. في لغات مثل C++ و C# و D و Java و Objective-C و Visual Basic (.NET) (VB.NET)، تُعد هذه الخصائص نتاجًا لنموذج البرمجة الكائنية التوجه في اللغة.
ردود الاتصال (ج)
تدعم بعض مكتبات لغة C دوال الاستدعاء . ويتم ذلك أحيانًا بتوفير قيمتين عند تسجيل دالة الاستدعاء في المكتبة: مؤشر دالة ومؤشر منفصل void*إلى بيانات يختارها المستخدم. عند تنفيذ المكتبة لدالة الاستدعاء، فإنها تمرر مؤشر البيانات. وهذا يمكّن دالة الاستدعاء من الاحتفاظ بحالتها والرجوع إلى المعلومات التي تم جمعها وقت تسجيلها في المكتبة. يشبه هذا الأسلوب الدوال المغلقة في الوظائف، ولكنه يختلف في الصيغة. فالمؤشر void*غير آمن من حيث النوع ، لذا يختلف هذا الأسلوب في لغة C عن الدوال المغلقة الآمنة من حيث النوع في لغات C# و Haskell و ML.
تُستخدم عمليات الاستدعاء على نطاق واسع في مجموعات أدوات واجهة المستخدم الرسومية (GUI) لتنفيذ البرمجة القائمة على الأحداث من خلال ربط الوظائف العامة للعناصر الرسومية (القوائم، والأزرار، ومربعات الاختيار، والمنزلقات، والدوارات، وما إلى ذلك) بوظائف خاصة بالتطبيق لتنفيذ السلوك المطلوب المحدد للتطبيق.
الدالة المتداخلة ومؤشر الدالة (C)
باستخدام امتداد مجموعة مُترجمات GNU (GCC)، يُمكن استخدام دالة مُتداخلة [ 15 ]adder ، ويُمكن لمؤشر الدالة مُحاكاة الإغلاقات، بشرط ألا تخرج الدالة من النطاق المُحتوي. المثال التالي غير صالح لأنه تعريف من المستوى الأعلى (اعتمادًا على إصدار المُترجم، قد يُنتج نتيجة صحيحة إذا تم تجميعه بدون تحسين، أي عند -O0):
#include <stdio.h>typedef int ( * FnIntToInt )( int ); // نوع الدالة int->intFnIntToInt adder ( int number ) { int add ( int value ) { return value + number ; } return & add ; // عامل & اختياري هنا لأن اسم الدالة في لغة C هو مؤشر يشير إلى نفسه }int main ( void ) { FnIntToInt add10 = adder ( 10 ); printf ( "%d \n " , add10 ( 1 )); return 0 ; }لكن تحريك adder(واختياريًا، typedef) mainيجعلها صالحة:
#include <stdio.h>typedef int ( * FnIntToInt )( int ); // نوع الدالة int->intint main ( void ) { FnIntToInt adder ( int number ) { int add ( int value ) { return value + number ; } return add ; } FnIntToInt add10 = adder ( 10 ); printf ( "%d \ n " , add10 ( 1 )); return 0 ; }إذا تم تنفيذ هذا الأمر، فسيتم الطباعة 11كما هو متوقع.
الفئات المحلية ووظائف لامدا (جافا)
تُمكّن لغة جافا من تعريف الأصناف داخل الدوال . تُسمى هذه الأصناف بالأصناف المحلية . وعندما لا تُسمى هذه الأصناف، تُعرف بالأصناف المجهولة (أو الأصناف الداخلية المجهولة ). يمكن للصنف المحلي (سواء كان مُسمى أو مجهولًا) أن يُشير إلى أسماء في الأصناف المُحيطة به، أو إلى متغيرات للقراءة فقط (مُعلّمة بـ final) في الدالة المُحيطة به.
استيراد java.net.URI ؛ استيراد javax.swing.JFrame ؛class CalculationWindow extends JFrame { private volatile int result ; // ... public void calculateInSeparateThread ( final URI uri ) { // التعبير "new Runnable() { ... }" هو فئة مجهولة تُنفذ واجهة 'Runnable'. Thread t = new Thread ( new Runnable () { @Override void run () { // يمكنه قراءة المتغيرات المحلية النهائية: calculate ( uri ); // يمكنه الوصول إلى الحقول الخاصة للفئة المُحيطة: result += 10 ; } } ); t . start (); } }تتيح خاصية التقاط finalالمتغيرات إمكانية التقاطها حسب قيمتها. حتى لو كان المتغير المراد التقاطه غير ثابت final، يمكن دائمًا نسخه إلى finalمتغير مؤقت قبل استدعاء الفئة مباشرةً.
يمكن محاكاة عملية التقاط المتغيرات بالمرجع باستخدام finalمرجع إلى حاوية قابلة للتغيير، مثل مصفوفة ذات عنصر واحد. لن يتمكن الصنف المحلي من تغيير قيمة مرجع الحاوية، ولكنه سيتمكن من تغيير محتويات الحاوية.
مع ظهور تعابير لامدا في جافا 8، [ 16 ] يؤدي الإغلاق إلى تنفيذ الكود أعلاه على النحو التالي:
استيراد java.net.URI ؛ استيراد javax.swing.JFrame ؛class CalculationWindow extends JFrame { private volatile int result ; // ... public void calculateInSeparateThread ( final URI uri ) { // The code () -> { /* code */ } is a closure. Thread t = new Thread (() -> { calculate ( uri ); result += 10 ; }); t . start (); } }تُعدّ الفئات المحلية أحد أنواع الفئات الداخلية التي تُصرّح داخل جسم الدالة. كما تدعم لغة جافا الفئات الداخلية المُصرّحة كأعضاء غير ثابتة في الفئة المُحيطة. [ 17 ] ويُشار إليها عادةً باسم "الفئات الداخلية". [ 18 ] تُعرّف هذه الفئات في جسم الفئة المُحيطة، ولها حق الوصول الكامل إلى متغيرات مثيل الفئة المُحيطة. ونظرًا لارتباطها بهذه المتغيرات، لا يُمكن إنشاء مثيل للفئة الداخلية إلا بربط صريح بمثيل من الفئة المُحيطة باستخدام صيغة خاصة. [ 19 ]
public class EnclosingClass { // تعريف الفئة الداخلية: public class InnerClass { public int incrementAndReturnCounter () { return counter ++ ; } }private int counter ; { counter = 0 ; }public int getCounter () { return counter ; }public static void main ( String [] args ) { EnclosingClass enclosingClassInstance = new EnclosingClass (); // إنشاء نسخة من الفئة الداخلية، مع ربطها بالمثيل EnclosingClass . InnerClass innerClassInstance = enclosingClassInstance . new InnerClass ();for ( int i = enclosingClassInstance.getCounter ( ) ; ( i = innerClassInstance.incrementAndReturnCounter ( ) ) < 10 ; // تم حذف خطوة الزيادة ) { System.out.println ( i ) ; } } }عند التنفيذ، سيتم طباعة الأعداد الصحيحة من 0 إلى 9. احذر من الخلط بين هذا النوع من الفئات والفئة المتداخلة، والتي يتم تعريفها بنفس الطريقة مع استخدام مُعدِّل "static"؛ فهذه الفئات لا تحقق التأثير المطلوب، بل هي مجرد فئات بدون ربط خاص مُعرَّف في فئة مُغلِّفة.
ابتداءً من Java 8 ، تدعم Java الدوال ككائنات من الدرجة الأولى. تُعتبر تعابير Lambda من هذا النوع من النوع java.util.function.Function<T, U>T، حيث يُمثل T المجال وU نوع الصورة. يمكن استدعاء التعبير باستخدام دالته Function.apply(T t)، ولكن ليس باستخدام استدعاء دالة قياسي.
استيراد java.util.function.Function ;دالة < سلسلة نصية ، عدد صحيح > الطول = s - > s . الطول ( ) ; النظام.الإخراج.طباعة ( الطول.تطبيق ( " مرحباً بالعالم!" ) ) ; // يطبع '13'Blocks (C, C++, Objective-C 2.0)
قدمت شركة Apple الكتل ، وهي شكل من أشكال الإغلاق، كامتداد غير قياسي في لغات C و C++ و Objective-C 2.0 وفي نظام التشغيل Mac OS X 10.6 "Snow Leopard" ونظام التشغيل iOS 4.0 . وقد أتاحت Apple تطبيقها لمترجمي GCC و clang.
تُعلَّم المؤشرات إلى الكتل والقيم الحرفية للكتل بعلامة ^. تُخزَّن المتغيرات المحلية العادية بالقيمة عند إنشاء الكتلة، وتكون للقراءة فقط داخل الكتلة. تُعلَّم المتغيرات التي سيتم تخزينها بالمرجع بعلامة __block. قد يلزم نسخ الكتل التي تحتاج إلى البقاء خارج النطاق الذي أُنشئت فيه. [ 20 ] [ 21 ]
typedef int ( ^ IntBlock )();دالة ` downCounter` من نوع ` int` تأخذ مصفوفة ` start` كمدخل ، وتعيد مصفوفة ` int` تحتوي على ` start` و` copy` و` autorelease` .IntBlock f = downCounter ( 5 ); NSLog ( @"%d" , f ()); NSLog ( @"%d" , f ()); NSLog ( @"%d" , f ());المندوبون (C#، VB.NET، D)
تدعم الطرق المجهولة في لغة C# وتعبيرات لامدا خاصية الإغلاق:
باستخدام System.Collections.Generic ؛int [] data = new [ ]{ 1 , 2 , 3 , 4 } ; int multiplier = 2 ; List <int> result = data.Select ( x = > x * multiplier ) ;يدعم Visual Basic .NET ، الذي يحتوي على العديد من ميزات اللغة المشابهة لتلك الموجودة في C#، تعبيرات لامدا مع الإغلاقات:
Dim data = { 1 , 2 , 3 , 4 } Dim multiplier = 2 Dim result = data . Select ( Function ( x ) x * multiplier )في لغة D ، يتم تنفيذ الإغلاقات بواسطة المندوبين، وهو مؤشر دالة مقترن بمؤشر سياق (على سبيل المثال مثيل فئة، أو إطار مكدس على الكومة في حالة الإغلاقات).
auto test1 () { int a = 7 ; return delegate () { return a + 3 ; }; // إنشاء مندوب مجهول }auto test2 () { int a = 20 ; int foo () { return a + 5 ; } // دالة داخلية return & foo ; // طريقة أخرى لإنشاء المندوب }void bar () { auto dg = test1 (); dg (); // 10 (حسنًا: test1.a موجود داخل دالة مغلقة وما زال موجودًا)dg = test2 (); dg (); // 25 (حسنًا: test2.a موجود داخل دالة مغلقة وما زال موجودًا) }يُوفر الإصدار الأول من لغة D دعمًا محدودًا للدوال المغلقة. على سبيل المثال، لن يعمل الكود المذكور أعلاه بشكل صحيح، لأن المتغير a موجود في مكدس الذاكرة، وبعد انتهاء الدالة test()، يصبح استخدامه غير صالح (على الأرجح، استدعاء الدالة foo عبر الدالة dg() سيعيد عددًا صحيحًا عشوائيًا). يمكن حل هذه المشكلة بتخصيص المتغير a صراحةً في الذاكرة الديناميكية، أو باستخدام هياكل البيانات أو الفئات لتخزين جميع المتغيرات المغلقة المطلوبة، وإنشاء دالة تفويض من دالة تُنفذ نفس الكود. يمكن تمرير الدوال المغلقة إلى دوال أخرى، طالما أنها تُستخدم فقط أثناء صلاحية القيم المُشار إليها (على سبيل المثال، استدعاء دالة أخرى باستخدام دالة مغلقة كمعامل رد نداء)، وهي مفيدة لكتابة كود معالجة بيانات عام ، لذا فإن هذا القيد، عمليًا، لا يُمثل مشكلة في أغلب الأحيان.
تم تدارك هذا القيد في الإصدار الثاني من لغة D، حيث يتم تخصيص المتغير 'a' تلقائيًا في الذاكرة الديناميكية (heap) لأنه يُستخدم في الدالة الداخلية، ويمكن لمندوب تلك الدالة الخروج من النطاق الحالي (عن طريق إسناد قيمة إلى dg أو إرجاع قيمة). أما المتغيرات المحلية الأخرى (أو الوسائط) التي لا يُشار إليها بواسطة المندوبين، أو التي يُشار إليها فقط بواسطة مندوبين لا يخرجون من النطاق الحالي، فتبقى في مكدس الذاكرة (stack)، وهو ما يُعد أبسط وأسرع من التخصيص في الذاكرة الديناميكية. وينطبق الأمر نفسه على توابع الفئة الداخلية التي تُشير إلى متغيرات الدالة.
كائنات الدوال (C++)
تُمكّن لغة C++ من تعريف كائنات الدوال عبر التحميل الزائد operator(). تتصرف هذه الكائنات بشكلٍ مشابه للدوال في لغات البرمجة الوظيفية. يمكن إنشاؤها أثناء التشغيل، وقد تحتوي على حالة، لكنها لا تستحوذ ضمنيًا على المتغيرات المحلية كما تفعل الدوال المغلقة. بدءًا من إصدار 2011 ، تدعم لغة C++ أيضًا الدوال المغلقة، وهي نوع من كائنات الدوال يتم إنشاؤها تلقائيًا من بنية لغوية خاصة تُسمى تعبير لامدا . يمكن للدالة المغلقة في C++ أن تستحوذ على سياقها إما بتخزين نسخ من المتغيرات التي يتم الوصول إليها كأعضاء في كائن الدالة المغلقة، أو بالمرجع. في الحالة الأخيرة، إذا خرج كائن الدالة المغلقة عن نطاق الكائن المُشار إليه، فإن استدعاءه operator()يُسبب سلوكًا غير مُحدد، لأن الدوال المغلقة في C++ لا تُمدد عمر سياقها.
استيراد std ؛باستخدام std :: string ؛ باستخدام std :: vector ؛void foo ( string & name ) { int n = 100 ; vector < string > v ; // ...// i من نوع vector<string>::iterator auto i = std::ranges::find_if(v, [&](const string& s)->bool { return s != name && s.size ( ) > n ; } ) ; // ' i ' الآن إما ' v.end ( ) ' أو يشير إلى السلسلة الأولى في ' v ' // التي لا تساوي ' name ' وطولها أكبر من 'n' }تحتوي لغة C++ أيضًا على كائن std::function<R(Args...)>لتخزين الدوال المجهولة. [ 22 ] ومع ذلك، نظرًا لأن الدوال المجهولة لها أسماء مُولَّدة بواسطة المُصرِّف وهي مُختلفة، فإن تخزين إحداها كـ std::functionيستخدم حذف النوع مما يُسبب عبئًا إضافيًا طفيفًا.
وكلاء مضمنون (إيفل)
تتضمن لغة إيفل عوامل مضمنة تُعرّف الدوال المغلقة. العامل المضمن هو كائن يُمثل روتينًا، ويتم تعريفه من خلال كتابة رمز الروتين بشكل مضمن. على سبيل المثال، في
ok_button.click_event.subscribe ( agent ( x , y : INTEGER ) do map.country_at_coordinates ( x , y ) .display end )الوسيط المُستخدم subscribeهو وكيل، يُمثل إجراءً ذا وسيطين؛ يحدد هذا الإجراء الدولة عند الإحداثيات المُقابلة ويعرضها. الوكيل بأكمله "مُشترك" في نوع الحدث click_eventلزر مُعين، بحيث كلما حدث هذا النوع من الأحداث على ذلك الزر - نتيجةً لنقر المستخدم عليه - سيتم تنفيذ الإجراء مع تمرير إحداثيات الفأرة كوسيطات للدالتين xو y.
يتمثل القيد الرئيسي لوكلاء إيفل، والذي يميزهم عن الدوال المغلقة في اللغات الأخرى، في عدم قدرتهم على الإشارة إلى المتغيرات المحلية من النطاق المحيط. يساعد هذا القرار التصميمي في تجنب الغموض عند الحديث عن قيمة متغير محلي في دالة مغلقة - هل يجب أن تكون أحدث قيمة للمتغير أم القيمة التي تم التقاطها عند إنشاء الوكيل؟ لا يمكن الوصول من داخل جسم الوكيل إلا إلى Currentمرجع للكائن الحالي (مماثل لـ `<object>` thisفي جافا)، وخصائصه، ومعاملاته. ويمكن تمرير قيم المتغيرات المحلية الخارجية بتوفير معاملات مغلقة إضافية للوكيل.
كلمة محجوزة __closure في C++Builder
يوفر Embarcadero C++Builder الكلمة المحجوزة__closure لتوفير مؤشر إلى طريقة ذات بنية مشابهة لمؤشر الدالة. [ 23 ]
تسمح لغة C القياسية بكتابة تعريف نوع (typedef) لمؤشر إلى نوع دالة باستخدام الصيغة التالية:
typedef void ( * TMyFunctionPointer )( void ); وبالمثل، typedefيمكن تعريف مؤشر إلى دالة باستخدام هذه الصيغة:
typedef void ( __closure * TMyMethodPointer )();انظر أيضاً
ملحوظات
- ↑ يمكن تخزين الدالة كمرجع لدالة ، مثل مؤشر الدالة .
- ↑ تشير هذه الأسماء عادةً إلى القيم أو المتغيرات القابلة للتغيير أو الدوال، ولكن يمكن أن تكون أيضًا كيانات أخرى مثل الثوابت أو الأنواع أو الفئات أو التصنيفات.
مراجع
- ↑ سوسمان وستيل. "Scheme: مترجم لحساب التفاضل والتكامل اللامدا الموسع". "... بنية بيانات تحتوي على تعبير لامدا، وبيئة تُستخدم عند تطبيق تعبير لامدا هذا على الوسائط." ( ويكي مصدر )
- ↑ تيرنر، ديفيد أ. (2012). "نبذة تاريخية عن لغات البرمجة الوظيفية" (ملف PDF) . الندوة الدولية حول اتجاهات البرمجة الوظيفية . سلسلة محاضرات في علوم الحاسوب. المجلد 7829. سبرينغر. الصفحات 1-20. انظر 12 §2، الملاحظة 8 للاطلاع على الادعاء المتعلق بتعبيرات M. doi : 10.1007/978-3-642-40447-4_1 . ISBN 978-3-642-40447-4.
- ^ لاندين، بي جيه (يناير 1964). “التقييم الميكانيكي للتعبيرات” (PDF) . مجلة الكمبيوتر . 6 (4): 308-320 . دوى : 10.1093/comjnl/6.4.308 .
- ↑ موسى، جويل (يونيو 1970). "وظيفة الدالة FUNCTION في لغة LISP، أو لماذا يجب تسمية مشكلة FUNARG بمشكلة البيئة". نشرة ACM SIGSAM (15): 13-27 . doi : 10.1145/1093410.1093411 . hdl : 1721.1/5854 . S2CID 17514262. AI Memo 199.
يُعدّ تشبيه الفرق بين FUNCTION وQUOTE في لغة LISP مفيدًا، حيث يمكن اعتبار QUOTE غطاءً مفتوحًا أو مساميًا للدالة، نظرًا لأن المتغيرات الحرة تنتقل إلى البيئة الحالية. أما FUNCTION فتعمل كغطاء مغلق أو غير مسامي (ومن هنا جاء مصطلح "الإغلاق" الذي استخدمه لاندين). لذا نتحدث عن تعابير لامدا "المفتوحة" (عادةً ما تكون الدوال في لغة ليسب تعابير لامدا) وتعابير لامدا "المغلقة". [...] بدأ اهتمامي بمشكلة البيئة عندما زار لاندين، الذي كان لديه فهم عميق للمشكلة، معهد ماساتشوستس للتكنولوجيا خلال عامي 1966-1967. حينها أدركتُ التطابق بين قوائم FUNARG، وهي نتائج تقييم تعابير لامدا "المغلقة" في
ليسب،
وإغلاقات لامدا في
ISWIM
.
- ^ ويكستروم، اكي (1987). البرمجة الوظيفية باستخدام Standard ML . برنتيس هول. رقم ISBN 0-13-331968-7
والسبب في تسميتها "إغلاقًا" هو أن التعبير الذي يحتوي على متغيرات حرة يسمى تعبيرًا "مفتوحًا"، ومن خلال ربط ارتباطات متغيراته الحرة به، فإنك تغلقه
. - ↑ سوسمان، جيرالد جاي ؛ ستيل، جاي إل. الابن (ديسمبر 1975). سكيم: مترجم لحساب لامدا الموسع (تقرير). مذكرة الذكاء الاصطناعي 349.
- ↑ أبيلسون، هارولد ؛ سوسمان، جيرالد جاي ؛ سوسمان، جولي (1996). بنية وتفسير برامج الحاسوب . مطبعة معهد ماساتشوستس للتكنولوجيا. ص 98-99 . ISBN 0-262-51087-1.
- ↑ "array.filter" . مركز مطوري موزيلا . 10 يناير 2010. تم الاطلاع عليه في 9 فبراير 2010 .
- ↑ "ردًا على: البرمجة الوظيفية، والبرمجة الكائنية، والعلاقات. هل يتفوق أحدها على الآخر؟" . 29 ديسمبر 1999. مؤرشف من الأصل في 26 ديسمبر 2008. تم الاطلاع عليه في 23 ديسمبر 2008 .
- ↑ تعابير لامدا والإغلاقات لجنة معايير لغة C++. 29 فبراير 2008.
- ↑ "6.4 الدوال المتداخلة" . دليل GCC .
إذا حاولت استدعاء الدالة المتداخلة من خلال عنوانها بعد انتهاء الدالة الحاوية، فستحدث مشكلة كبيرة. إذا حاولت استدعاءها بعد انتهاء مستوى نطاق الدالة الحاوية، وكانت تشير إلى بعض المتغيرات التي لم تعد ضمن النطاق، فقد يحالفك الحظ، ولكن ليس من الحكمة المخاطرة. أما إذا لم تكن الدالة المتداخلة تشير إلى أي شيء خارج النطاق، فستكون في مأمن.
- ↑ ستظل أسس دلالات الفاعل راسخة. أطروحة دكتوراه في الرياضيات، معهد ماساتشوستس للتكنولوجيا. يونيو 1981.
- ↑ "Function.prototype.bind()" . وثائق MDN على الويب . تم الاطلاع عليه بتاريخ 20 نوفمبر 2018 .
- ↑ "الإغلاقات" . مستندات MDN على الويب . تم الاطلاع عليه بتاريخ 20 نوفمبر 2018 .
- ↑ "الوظائف المتداخلة" .
- ↑ "تعبيرات لامدا" . دروس جافا التعليمية .
- ↑ "الفئات المتداخلة، والداخلية، والعضوية، وذات المستوى الأعلى" . مدونة جوزيف د. دارسي لأوراكل . يوليو 2007. مؤرشف من الأصل في 31 أغسطس 2016.
- ↑ "مثال على الفئة الداخلية" . دروس جافا التعليمية: تعلم لغة جافا: الفئات والكائنات .
- ↑ "الفئات المتداخلة" . دروس جافا: تعلم لغة جافا: الفئات والكائنات .
- ↑ "مواضيع برمجة الكتل" . شركة آبل. 8 مارس 2011. تم الاطلاع عليه في 8 مارس 2011 .
- ↑ بنغتسون، يواكيم (7 يوليو 2010). "البرمجة باستخدام لغة C Blocks على أجهزة أبل" . مؤرشف من الأصل في 25 أكتوبر 2010. تم الاطلاع عليه في 18 سبتمبر 2010 .
- ↑ "std::function - cppreference.com" . cppreference.com . تم الاطلاع عليه بتاريخ 2 سبتمبر 2025 .
- ↑ يمكن الاطلاع على الوثائق الكاملة على الرابط التالي: http://docwiki.embarcadero.com/RADStudio/Rio/en/Closure
روابط خارجية
- "أوراق لامدا الأصلية" : سلسلة كلاسيكية من الأوراق التي كتبها جاي إل ستيل جونيور وجيرالد جاي سوسمان والتي تناقش، من بين أمور أخرى، تنوع الإغلاقات في سياق Scheme (حيث تظهر كتعبيرات لامدا ).
- جافتر، نيل (28 يناير 2007). "تعريف الإغلاقات" .
- براخا، جلعاد ؛ جافتر، نيل؛ جوسلينج، جيمس ؛ فون دير أهي، بيتر. "الإغلاقات للغة برمجة جافا (الإصدار 0.5)" .
- الإغلاقات : مقال حول الإغلاقات في لغات البرمجة الإجرائية ذات الكتابة الديناميكية ، بقلم مارتن فاولر .
- أساليب إغلاق المجموعات : مثال على مجال تقني يكون فيه استخدام الإغلاقات مناسبًا، بقلم مارتن فاولر.
- مفاهيم لغة البرمجة
- تطبيق لغات البرمجة الوظيفية
- البرامج الفرعية
