كائن الدالة

في برمجة الحاسوب ، يُعدّ كائن الدالة [ a ] بنيةً تسمح باستدعاء كائن كما لو كان دالة عادية ، وعادةً ما يكون له نفس الصيغة (معامل دالة يمكن أن يكون دالة أيضًا). في بعض اللغات، وخاصةً C++، تُسمى كائنات الدوال غالبًا بالدوال الوظيفية (وهي ليست مرتبطة بمفهوم البرمجة الوظيفية ).

وصف

يُستخدم كائن الدالة عادةً في كتابة دوال الاستدعاء . في اللغات الإجرائية ، مثل لغة C ، يُمكن تنفيذ الاستدعاء باستخدام مؤشرات الدوال . [ 2 ] مع ذلك، قد يكون تمرير حالة إلى دالة الاستدعاء أو منها أمرًا صعبًا أو غير عملي. هذا القيد يُعيق أيضًا السلوك الديناميكي للدالة. يحل كائن الدالة هذه المشاكل، إذ أن الدالة في الواقع واجهة لكائن كامل، يحمل حالته الخاصة.

تدعم العديد من لغات البرمجة الحديثة (وبعض اللغات القديمة)، مثل C++ و Eiffel و Groovy و Lisp و Smalltalk و Perl و PHP و Python و Ruby و Scala وغيرها الكثير، كائنات الدوال من الدرجة الأولى ، بل وقد تستخدمها بشكل كبير. [ 3 ] كما تدعم لغات البرمجة الوظيفية الدوال المغلقة ، أي الدوال من الدرجة الأولى التي يمكنها "إغلاق" المتغيرات في بيئتها المحيطة عند إنشائها. أثناء عملية الترجمة، تُحوّل عملية تُعرف باسم رفع لامدا الدوال المغلقة إلى كائنات دوال.

في لغتي C و C++

لنأخذ مثالاً على روتين فرز يستخدم دالة رد نداء لتحديد علاقة ترتيب بين عنصرين. يستخدم برنامج C/C++ التالي مؤشرات الدوال:

#include <stdlib.h>// دالة رد نداء qsort() // تُرجع < 0 إذا كان a < b، و > 0 إذا كان a > b، و 0 إذا كان a == b int compareInts ( const void * a , const void * b ) { return ( * ( int * ) a - * ( int * ) b ); }// ...// النموذج الأولي للدالة qsort هو // void qsort(void* base, size_t nel, size_t width, int (*compar)(const void*, const void*));// ...int main ( void ) { int items [] = { 4 , 3 , 1 , 2 }; qsort ( items , sizeof ( items ) / sizeof ( items [ 0 ]), sizeof ( items [ 0 ]), compareInts ); return 0 ; }

في لغة C++، يمكن استخدام كائن دالة بدلاً من دالة عادية عن طريق تعريف فئة تُحمّل عامل استدعاء الدالة من خلال تعريف operator()دالة عضو. في لغة C++، قد يظهر هذا على النحو التالي:

استيراد std ؛باستخدام std :: vector ;// دالة المقارنة: تُرجع القيمة true إذا كان a < b، و false خلاف ذلك. class IntegerComparator { public : bool operator ()( const int & a , const int & b ) const { return a < b ; } };int main () { vector < int > items = { 4 , 3 , 1 , 2 }; std :: ranges :: sort ( items , IntegerComparator ()); return 0 ; }

لاحظ أن صيغة استدعاء الدالة std::sort()هي نفسها، ولكن يتم تمرير كائن بدلاً من مؤشر دالة. عند استدعاء الدالة، تُنفَّذ دالة الاستدعاء تمامًا كأي دالة عضو أخرى، وبالتالي تتمتع بصلاحية الوصول الكامل إلى الأعضاء الأخرى (البيانات أو الدوال) للكائن. بالطبع، هذا مجرد مثال بسيط. لفهم قوة الدالة المُحوِّلة (functor) مقارنةً بالدالة العادية، لنأخذ مثالًا شائعًا وهو فرز الكائنات حسب حقل معين. في المثال التالي، تُستخدم دالة مُحوِّلة لفرز قاعدة بيانات بسيطة للموظفين حسب رقم هوية كل موظف.

استيراد std ؛باستخدام std :: vector ;فئة تعداد SortField { الاسم ، العمر ، المعرف }class CompareBy { private : const SortField SORT_FIELD ; public : explicit CompareBy ( SortField field ) : SORT_FIELD { field } { } bool operator ()( const Employee & a , const Employee & b ) const { switch ( SORT_FIELD ) { case SortField :: NAME : return a.getName ( ) < b.getName ( ); case SortField :: AGE : return a.getAge ( ) < b.getAge ( ) ; case SortField :: ID : return a.getId ( ) < b.getId ( ) ; default : std :: unreachable ( ) ; } } } ;int main () { vector < Employee > employees ; // كود لملء قاعدة البيانات // فرز قاعدة البيانات حسب رقم هوية الموظف std :: range :: sort ( employees , CompareBy ( SortField :: ID )); return 0 ; }

إن استخدام تعبير لامدا (الذي تم تقديمه في C++11 ) يوفر طريقة أكثر إيجازًا للقيام بنفس الشيء.

استيراد std ؛باستخدام std :: vector ;int main () { vector < Employee > employees ;// كود لتعبئة قاعدة البياناتconst SortField field = SortField :: ID ; std :: ranges :: sort ( employee , [ & field ]( const Employee & a , const Employee & b ) const- > bool { /* code to select and compare field */ }); return 0 ; }

من الممكن استخدام كائنات الدوال في حالات أخرى غير دوال الاستدعاء. في هذه الحالة، لا يُستخدم مصطلح " دالة" عادةً لوصف كائن الدالة. لنكمل المثال،

IntegerComparator cpm ; bool result = cpm ( a , b );

إلى جانب دوال أنواع الفئات، يُمكن إنشاء أنواع أخرى من كائنات الدوال في لغة C++. وتستفيد هذه الكائنات من ميزات مؤشرات الأعضاء والقوالب في C++ . وتتيح مرونة القوالب استخدام بعض تقنيات البرمجة الوظيفية ، مثل تعريف كائنات الدوال بدلالة كائنات دوال أخرى (مثل تركيب الدوال ). ويعتمد جزء كبير من مكتبة القوالب القياسية للغة C++ (STL) على كائنات الدوال القائمة على القوالب.

هناك طريقة أخرى لإنشاء كائن دالة في لغة C++، وهي تعريف دالة تحويل غير صريحة إلى نوع مؤشر دالة، أو نوع مرجع دالة ، أو مرجع إلى نوع مؤشر دالة. بافتراض أن التحويل لا يتجاهل مُحدِّدات cv ، فإن هذا يسمح باستخدام كائن من هذا النوع كدالة بنفس توقيع النوع الذي تم تحويله إليه. بتعديل مثال سابق لاستخدام هذه الطريقة، نحصل على الفئة التالية، التي يمكن استدعاء مثيلاتها كمؤشرات دوال: [ 4 ]

استيراد std ؛باستخدام std :: vector ;// دالة المقارنة: تُرجع القيمة true إذا كان a < b، و false خلاف ذلك. class IntegerComparator { public : static bool compare ( const int & a , const int & b ) noexcept { return a < b ; }باستخدام CompareFn = decltype ( compare );operator CompareFn * () const { return compare ; } };int main () { vector < int > items = { 4 , 3 , 1 , 2 }; std :: ranges :: sort ( items , IntegerComparator ()); return 0 ; }

الحفاظ على الحالة

من مزايا كائنات الدوال الأخرى قدرتها على الحفاظ على حالة تؤثر operator()بين الاستدعاءات. على سبيل المثال، يُعرّف الكود التالي مولدًا يعدّ من 10 تصاعديًا ويتم استدعاؤه 11 مرة.

استيراد std ؛باستخدام std :: cout ؛ باستخدام std :: ostream_iterator ؛class CountFrom { private : int count ; public : CountFrom ( int count ) : count { count } {} int operator ()() { return count ++ ; } };int main () { const int state = 10 ; std :: generate_n ( ostream_iterator < int > ( cout , " \n " ), 11 , CountFrom ( state ) ); }

في لغة C++14 أو الإصدارات الأحدث، يمكن إعادة كتابة المثال أعلاه على النحو التالي:

استيراد std ؛باستخدام std :: cout ؛ باستخدام std :: ostream_iterator ؛int main () { std :: generate_n ( ostream_iterator < int > ( cout , " \n " ), 11 , [ count = 10 ]() mutable -> int { return count ++ ; } ); }

شركة#

في لغة C# ، تُعرَّف كائنات الدوال باستخدام المندوبين . يمكن تعريف المندوب باستخدام دالة مُسماة أو تعبير لامدا . إليك مثال باستخدام دالة مُسماة.

باستخدام System ؛ باستخدام System.Collections.Generic ؛دالة المقارنة الثابتة ( int x , int y ) { return x - y ; }List <int> items = new ( 4 , 3 , 1 , 2 ) ; Comparison <int> del = CompareFunction ; items.Sort ( del ) ;

إليك مثال باستخدام تعبير لامدا.

باستخدام System ؛ باستخدام System.Collections.Generic ؛List <int> items = new ( 4 , 3 , 1 , 2 ) ; items.Sort ( ( x , y ) = > x - y ) ;

في D

توفر لغة D عدة طرق لتعريف كائنات الدوال: على غرار لغة Lisp/Python عبر الإغلاقات أو على غرار لغة C# عبر المندوبين ، على التوالي:

دالة منطقية find ( T )( T [] haystack , دالة منطقية delegate ( T ) needle_test ) { foreach ( straw ; haystack ) { if ( needle_test ( straw )) { return true ; } } return false ; }void main () { int [] haystack = [ 345 , 15 , 457 , 9 , 56 , 123 , 456 ]; int needle = 123 ; bool needleTest ( int n ) { return n == needle ; } assert ( find ( haystack , & needleTest )); }

يُحدد المُصرّف الفرق بين المُفوَّض والإغلاق في لغة D تلقائيًا وبشكلٍ مُتحفظ. كما تدعم لغة D أيضًا الدوال الحرفية، التي تسمح بتعريفها على غرار تعبيرات لامدا.

void main () { int [] haystack = [ 345 , 15 , 457 , 9 , 56 , 123 , 456 ]; int needle = 123 ; assert ( find ( haystack , ( int n ) { return n == needle ; })); }

للسماح للمترجم بتضمين الكود (انظر أعلاه)، يمكن أيضًا تحديد كائنات الدوال بأسلوب C++ عبر تحميل المعاملات الزائدة :

دالة منطقية find ( T , F )( T [] haystack , F needle_test ) { foreach ( straw ; haystack ) { if ( needle_test ( straw )) { return true ; } } return false ; }void main () { int [] haystack = [ 345 , 15 , 457 , 9 , 56 , 123 , 456 ]; int needle = 123 ; class NeedleTest { int needle ;this ( int n ) { needle = n ; }bool opCall ( int n ) { return n == needle ; } } assert ( find ( haystack , new NeedleTest ( needle ))); }

في إيفل

في منهجية ولغة تطوير البرمجيات إيفل ، تُعتبر العمليات والكائنات مفاهيم منفصلة. مع ذلك، تُسهّل آلية الوكلاء نمذجة العمليات ككائنات وقت التشغيل. تُلبي الوكلاء نطاق التطبيقات المنسوبة إلى كائنات الدوال، مثل تمريرها كوسائط في استدعاءات الإجراءات أو تحديدها كإجراءات رد نداء. يُحاول تصميم آلية الوكلاء في إيفل عكس الطبيعة الكائنية التوجه للمنهجية واللغة. الوكيل هو كائن يُمثل عادةً نسخة مباشرة من أحد فئتي المكتبة اللتين تُنمذجان نوعي الإجراءات في إيفل: PROCEDUREو FUNCTION. تنحدر هاتان الفئتان من الفئة الأكثر تجريدًا ROUTINE.

في لغة البرمجة، تسمح الكلمة المفتاحية agentبإنشاء عناصر برمجية بشكل مختصر. في المثال التالي، الهدف هو إضافة إجراء تحريك المؤشر للأمام إلى قائمة الإجراءات التي سيتم تنفيذها عند النقر على زر.

my_button . select_actions . extend ( agent my_gauge . step_forward )

الروتين extendالمشار إليه في المثال أعلاه هو ميزة من ميزات فئة في مكتبة واجهة المستخدم الرسومية (GUI) لتوفير إمكانيات البرمجة القائمة على الأحداث .

في فئات مكتبات أخرى، تُستخدم العوامل لأغراض مختلفة. ففي مكتبة تدعم هياكل البيانات، على سبيل المثال، تُطبّق فئة تُحاكي الهياكل الخطية التكميم الشامل باستخدام دالة for_allمن نوع BOOLEANتقبل عاملاً، وهو نسخة من FUNCTION، كوسيط. لذا، في المثال التالي، my_actionتُنفّذ الدالة فقط إذا احتوت جميع عناصر my_listعلى الحرف '!'.

my_list : LINKED_LIST [ STRING ] ... إذا كانت my_list . for_all ( agent { STRING }. has ( '!' )) ثم my_action end ...

عند إنشاء الوكلاء، يمكن أن تكون وسائط الإجراءات التي يمثلونها، وحتى الكائن المستهدف الذي تُطبق عليه، مغلقة أو مفتوحة . تُعطى قيم للوسائط والأهداف المغلقة عند إنشاء الوكيل. أما تعيين قيم الوسائط والأهداف المفتوحة فيؤجل إلى وقت لاحق بعد إنشاء الوكيل. for_allيتوقع الإجراء وسيطًا من وكيل يمثل دالة ذات وسيط أو هدف مفتوح واحد يتوافق مع المعامل العام الفعلي للبنية ( STRINGفي هذا المثال).

عندما يُترك هدف أحد العوامل مفتوحًا، يُستبدل اسم فئة الهدف المتوقع، الموجود بين قوسين معقوفين، بمرجع الكائن كما هو موضح في النص agent {STRING}.has ('!')في المثال أعلاه. وعندما تُترك وسيطة مفتوحة، تُستخدم علامة الاستفهام (؟) كعنصر نائب للوسيطة المفتوحة.

تهدف إمكانية إغلاق الأهداف والوسائط أو تركها مفتوحة إلى تحسين مرونة آلية الوكيل. لنفترض وجود فئة تحتوي على الإجراء التالي لطباعة سلسلة نصية على المخرج القياسي بعد سطر جديد:

print_on_new_line ( s : STRING ) -- اطبع `s` مسبوقًا بسطر جديد do print ( "%N" + s ) end

يستخدم المقتطف التالي، الذي يفترض أنه من نفس الفئة، print_on_new_lineلتوضيح خلط الوسائط المفتوحة والأهداف المفتوحة في العوامل المستخدمة كوسائط لنفس الروتين.

my_list : LINKED_LIST [ STRING ] ... my_list . do_all ( agent print_on_new_line ( ? )) my_list . do_all ( agent { STRING }. to_lower ) my_list . do_all ( agent print_on_new_line ( ? )) ...

يستخدم هذا المثال الإجراء do_allالخاص بالهياكل الخطية، والذي ينفذ الروتين الذي تم نمذجته بواسطة وكيل لكل عنصر في الهيكل.

تقوم سلسلة التعليمات الثلاث بطباعة السلاسل النصية في my_list، ثم تحويل السلاسل النصية إلى أحرف صغيرة، ثم طباعتها مرة أخرى.

يتكرر الإجراء do_allعبر الهيكل لتنفيذ الروتين واستبدال العنصر الحالي إما بالوسيط المفتوح (في حالة الوكلاء المستندين إلى print_on_new_line)، أو بالهدف المفتوح (في حالة الوكيل المستند إلى to_lower).

كما تسمح الوسائط والأهداف المفتوحة والمغلقة باستخدام إجراءات تتطلب وسائط أكثر من اللازم عن طريق إغلاق جميع الوسائط باستثناء العدد الضروري منها:

my_list.do_all ( agent my_multi_arg_procedure ( closed_arg_1 , ? , closed_arg_2 , closed_arg_3 )

تم تفصيل آلية وكيل إيفل في وثيقة معيار إيفل ISO/ECMA .

في لغة جافا

لا تحتوي لغة جافا على وظائف من الدرجة الأولى ، لذلك يتم التعبير عن كائنات الوظائف عادةً بواسطة واجهة ذات طريقة واحدة ( Callableالواجهة الأكثر شيوعًا)، وعادةً ما يكون التنفيذ عبارة عن فئة داخلية مجهولة ، أو، بدءًا من جافا 8، دالة لامدا .

كمثال من مكتبة جافا القياسية، java.util.Collections.sort()تأخذ الدالة `a` Listودالة أخرى دورها مقارنة العناصر في القائمة. وبدون دوال من الدرجة الأولى، تُعتبر الدالة `a` جزءًا من واجهة `Comparator`. ويمكن استخدامها على النحو التالي.

List <String> list = Arrays.asList ( " 10" , " 1" , "20" , " 11" , " 21" , "12" );Comparator <String> numStringComparator = new Comparator <String> ( ) { public int compare ( String str1 , String str2 ) { return Integer.valueOf ( str1 ) .compareTo ( Integer.valueOf ( str2 ) ) ; } } ;Collections.sort ( list , numStringComparator ) ;

في Java 8+، يمكن كتابة ذلك على النحو التالي:

List <String> list = Arrays.asList ( " 10" , " 1" , "20" , " 11" , " 21" , "12" );Comparator <String> numStringComparator = ( str1 , str2 ) - > Integer.valueOf ( str1 ) .compareTo ( Integer.valueOf ( str2 ) ) ;Collections.sort ( list , numStringComparator ) ;

في جافا سكريبت

في لغة جافا سكريبت ، تُعتبر الدوال كائنات من الدرجة الأولى. كما تدعم جافا سكريبت الإغلاقات.

قارن ما يلي بمثال بايثون اللاحق.

دالة المُجمِّع ( البداية ) { دع الحالي = البداية إرجاع دالة ( س ) { إرجاع الحالي += س } }

مثال على ذلك في الاستخدام:

ليكن a = المُجمِّع ( 4 ) ليكن x = a ( 5 ) // قيمة x هي 9 ليكن x = a ( 2 ) // قيمة x هي 11ليكن b = Accumulator ( 42 ) x = b ( 7 ) // قيمة x هي 49 (القيمة الحالية = 49 في الدالة المغلقة b) x = a ( 7 ) // قيمة x هي 18 (القيمة الحالية = 18 في الدالة المغلقة a)

في جوليا

في لغة جوليا ، ترتبط الدوال بالأنواع، لذا من الممكن جعل أي كائن جوليا "قابلاً للاستدعاء" بإضافة دوال إلى نوعه. (تُسمى هذه الكائنات "القابلة للاستدعاء" أحيانًا "دوالًا وظيفية").

ومن الأمثلة على ذلك هذا الهيكل القابل للتغيير للمراكم (استنادًا إلى دراسة بول جراهام حول بناء جملة لغة البرمجة ووضوحها): [ 5 ]

julia> mutable struct Accumulator n :: Int endjulia> function ( acc :: Accumulator )( n2 ) acc . n += n2 endjulia> a = Accumulator ( 4 ) Accumulator(4)julia> a ( 5 ) 9julia> a ( 2 ) 11julia> b = Accumulator ( 42 ) Accumulator(42)julia> b ( 7 ) 49

يمكن أيضًا تنفيذ هذا المُجمِّع باستخدام الإغلاق:

julia> function Accumulator ( n0 ) n = n0 function ( n2 ) n += n2 end end Accumulator (generic function with 1 method)julia> a = Accumulator ( 4 ) (::#1) (دالة عامة ذات طريقة واحدة)julia> a ( 5 ) 9julia> a ( 2 ) 11julia> b = Accumulator ( 42 ) (::#1) (دالة عامة ذات طريقة واحدة)julia> b ( 7 ) 49

في لغة ليسب ولغة سكيم

في لغات عائلة ليسب، مثل كومون ليسب وسكيم وغيرها ، تُعتبر الدوال كائنات، تمامًا مثل السلاسل النصية والمتجهات والقوائم والأعداد. يُنشئ عامل بناء الإغلاق كائن دالة من جزء من البرنامج: جزء الكود المُعطى كوسيط للعامل هو جزء من الدالة، وكذلك البيئة المعجمية: يتم التقاط روابط المتغيرات المرئية معجميًا وتخزينها في كائن الدالة، والذي يُسمى عادةً بالإغلاق . تؤدي الروابط الملتقطة دور متغيرات الأعضاء ، بينما يؤدي جزء الكود من الإغلاق دور دالة العضو المجهولة ، تمامًا مثل عامل () في لغة C++.

يحتوي مُنشئ الإغلاق على الصيغة التالية (lambda (parameters ...) code ...). (parameters ...)يسمح الجزء الأول بتعريف واجهة، بحيث تأخذ الدالة المعاملات المُعلنة. أما code ...الجزء الثاني فيتكون من تعابير يتم تقييمها عند استدعاء الدالة.

تُعدّ العديد من استخدامات الدوال في لغات مثل C++ مجرد محاكاة لوظيفة إنشاء الإغلاق المفقودة. ولأن المبرمج لا يستطيع إنشاء إغلاق مباشرةً، فعليه تعريف فئة تحتوي على جميع متغيرات الحالة اللازمة، بالإضافة إلى دالة عضوية. ثم يقوم بإنشاء نسخة من تلك الفئة، مع ضمان تهيئة جميع المتغيرات العضوية من خلال دالة الإنشاء الخاصة بها. وتُستمد القيم تحديدًا من تلك المتغيرات المحلية التي ينبغي أن يتم التقاطها مباشرةً بواسطة الإغلاق.

كائن دالة يستخدم نظام الفئات في لغة Common Lisp، بدون استخدام الإغلاقات:

( defclass counter () (( value :initarg :value :accessor value-of )))( استدعاء الدالة defmethod ( ( c counter )) ( incf ( قيمة c )))( defun make-counter ( initial-value ) ( make-instance 'counter :value initial-value ))استخدم العداد: ( defvar *c* ( make-counter 10 )) ( functor-call *c* ) --> 11 ( functor-call *c* ) --> 12

بما أنه لا توجد طريقة قياسية لإنشاء كائنات قابلة للاستدعاء الوظيفي في لغة Common Lisp، فإننا نحاكي ذلك بتعريف دالة عامة تُسمى FUNCTOR-CALL. يمكن تخصيص هذه الدالة لأي فئة على الإطلاق. أما دالة FUNCALL القياسية فهي ليست عامة؛ إذ لا تقبل إلا كائنات الدوال.

إنّ دالة FUNCTOR-CALL العامة هي التي تُتيح لنا إنشاء كائنات الدوال، وهي بنية برمجية تسمح باستدعاء كائن ما كما لو كان دالة عادية، وعادةً ما يكون ذلك بنفس الصيغة. لدينا صيغة متشابهة تقريبًا : FUNCTOR-CALL بدلًا من FUNCALL. توفر بعض لغات Lisp كائنات قابلة للاستدعاء باستخدام funcallable كإضافة بسيطة. إنّ جعل الكائنات قابلة للاستدعاء باستخدام نفس صيغة الدوال أمرٌ في غاية السهولة. إنّ جعل عامل استدعاء الدالة يعمل مع أنواع مختلفة من الدوال ، سواءً كانت كائنات فئوية أو دوال مغلقة، ليس أكثر تعقيدًا من جعل عامل الجمع (+) يعمل مع أنواع مختلفة من الأرقام، مثل الأعداد الصحيحة أو الحقيقية أو المركبة.

الآن، إليك عداد مُنفَّذ باستخدام دالة مغلقة. هذا أكثر اختصارًا ومباشرة. يتم التقاط وسيط القيمة الابتدائية (INITIAL-VALUE) لدالة إنشاء العداد (MAKE-COUNTER) واستخدامه مباشرةً. لا حاجة لنسخه إلى كائن فئة مساعد عبر مُنشئ. إنه العداد نفسه. يتم إنشاء كائن مساعد، ولكن ذلك يحدث في الخلفية .

( defun make-counter ( value ) ( lambda () ( incf value )))استخدم العداد ( defvar *c* ( make-counter 10 )) ( funcall *c* ) ; --> 11 ( funcall *c* ) ; --> 12

يجعل Scheme عمليات الإغلاق أبسط، ويميل كود Scheme إلى استخدام البرمجة عالية المستوى هذه بطريقة أكثر اصطلاحية.

( define ( make-counter value ) ( lambda () ( set! value ( + value 1 )) value )) ;;; استخدم العداد ( define c ( make-counter 10 )) ( c ) ; --> 11 ( c ) ; --> 12

يمكن إنشاء أكثر من دالة مغلقة في نفس البيئة المعجمية. يستطيع متجه من الدوال المغلقة، تُنفذ كل منها نوعًا محددًا من العمليات، محاكاة كائن يحتوي على مجموعة من العمليات الافتراضية بدقة عالية. هذا النوع من البرمجة الكائنية التوجه أحادية الإرسال يُمكن تنفيذه بالكامل باستخدام الدوال المغلقة.

وهكذا، يُحفر نفقٌ من كلا جانبي الجبل المجازي. يكتشف المبرمجون في لغات البرمجة الكائنية التوجه كائنات الدوال بتقييد الكائنات بحيث يكون لها دالة رئيسية واحدة تؤدي غرضها الوظيفي، بل ويحذفون اسمها ليبدو وكأن الكائن يُستدعى! بينما لا يستغرب المبرمجون الذين يستخدمون الإغلاقات استدعاء كائن كدالة، إلا أنهم يكتشفون أن إغلاقات متعددة تشترك في نفس البيئة يمكن أن توفر مجموعة كاملة من العمليات المجردة، مثل جدول افتراضي، لنوع الإرسال الأحادي في البرمجة الكائنية التوجه.

في لغة Objective-C

في لغة Objective-C ، يُمكن إنشاء كائن دالة من NSInvocationالفئة. يتطلب إنشاء كائن دالة تحديد توقيع الدالة، والكائن المستهدف، ومحدد الهدف. إليك مثال لإنشاء استدعاء للكائن الحالي myMethod:

// إنشاء كائن دالة SEL sel = @selector ( myMethod ); NSInvocation * inv = [ NSInvocation invocationWithMethodSignature : [ self methodSignatureForSelector : sel ]]; [ inv setTarget : self ]; [ inv setSelector : sel ];// قم بتنفيذ الاستدعاء الفعلي [ inv invoke ];

من مزايا NSInvocationذلك إمكانية تعديل الكائن المستهدف بعد إنشائه. NSInvocationيمكن إنشاء كائن واحد ثم استدعاؤه لكل عدد من الكائنات المستهدفة، على سبيل المثال من كائن قابل للمراقبة. NSInvocationيمكن إنشاء كائن من بروتوكول فقط، لكن ذلك ليس بالأمر السهل. انظر هنا .

في لغة بيرل

في لغة بيرل ، يمكن إنشاء كائن دالة إما من مُنشئ فئة يُعيد دالة مغلقة على بيانات مثيل الكائن، أو يتم إدخالها في الفئة:

package Acc1 ; sub new { my $class = shift ; my $arg = shift ; my $obj = sub { my $num = shift ; $arg += $num ; }; bless $obj , $class ; } 1 ;

أو عن طريق تحميل &{}المعامل بشكل زائد بحيث يمكن استخدام الكائن كدالة:

package Acc2 ; use overload '&{}' => sub { my $self = shift ; sub { my $num = shift ; $self -> { arg } += $num ; } };sub new { my $class = shift ; my $arg = shift ; my $obj = { arg => $arg }; bless $obj , $class ; } 1 ;

في كلتا الحالتين، يمكن استخدام كائن الدالة إما باستخدام صيغة السهم لفك المرجع $ref->(@arguments) :

استخدم Acc1 ؛ my $a = Acc1- > new ( 42 print( $a -> ( 10 ), "\n") ؛ # يطبع 52؛ print( $a -> ( 8 ), "\n") ؛ # يطبع 60

أو باستخدام صيغة فك المرجع البرمجي &$ref(@arguments) :

استخدم Acc2 ؛ my $a = Acc2- > new ( 12 print & $a ( 10 ), "\n" ؛ # يطبع 22 print & $a ( 8 ), "\n" ؛ # يطبع 30

في لغة PHP

يحتوي PHP 5.3+ على دوال من الدرجة الأولى يمكن استخدامها كمعامل للدالة على سبيل المثال usort():

$a = array ( 3 , 1 , 4 ); usort ( $a , function ( $x , $y ) { return $x - $y ; });

يدعم PHP 5.3+ أيضًا وظائف لامدا والإغلاقات.

دالة المُجمِّع ( $start ) { $current = $start ; return دالة ( $x ) use ( & $current ) { return $current += $x ; }; }

مثال على ذلك في الاستخدام:

$a = Accumulator ( 4 ); $x = $a ( 5 ); echo "x = $x <br/>" ; // x = 9 $x = $a ( 2 ); echo "x = $x <br/>" ; // x = 11

من الممكن أيضًا في PHP 5.3+ جعل الكائنات قابلة للاستدعاء عن طريق إضافة __invoke()طريقة سحرية إلى فئتها: [ 6 ]

class Minus { public function __invoke ( $x , $y ) { return $x - $y ; } }$a = array ( 3 , 1 , 4 ); usort ( $a , new Minus ());

في باور شيل

في لغة ويندوز باور شيل ، كتلة البرمجة النصية هي مجموعة من العبارات أو التعبيرات التي يمكن استخدامها كوحدة واحدة. تقبل كتلة البرمجة النصية وسائط وتعيد قيمًا. وهي نسخة من نوع System.Management.Automation.ScriptBlock في إطار عمل مايكروسوفت .NET .

دالة Get-Accumulator ( $x ) { { param ( $y ) return $x += $y }. GetNewClosure () }
PS C:\> $a = Get-Accumulator 4 PS C:\> & $a 5 9 PS C:\> & $a 2 11 PS C:\> $b = Get-Accumulator 32 PS C:\> & $b 10 42

في لغة بايثون

في لغة بايثون ، تُعتبر الدوال كائنات من الدرجة الأولى، تمامًا مثل السلاسل النصية والأرقام والقوائم، إلخ. هذه الميزة تُغني عن كتابة كائن دالة في كثير من الحالات. __call__()يمكن استدعاء أي كائن يحتوي على دالة باستخدام صيغة استدعاء الدالة.

ومن الأمثلة على ذلك فئة المُجمِّع هذه (استنادًا إلى دراسة بول جراهام حول بناء جملة لغة البرمجة ووضوحها): [ 7 ]

class Accumulator : def __init__ ( self , n : int ) -> None : self . n = ndef __call __ ( self , x : int ) - > int : self.n + = x return self.n

مثال على ذلك في الاستخدام:

أ : المُجمِّع = المُجمِّع ( 4 ) اطبع ( أ ( 5 )) عدد مرات الطباعة: 9 اطبع ( أ ( 2 )) عدد مرات الطباعة: 11 ب : المُجمِّع = المُجمِّع ( 42 ) اطبع ( ب ( 7 )) عدد مرات الطباعة: 49

بما أن الدوال هي كائنات، فإنه يمكن تعريفها محليًا، وإعطاؤها سمات، وإرجاعها بواسطة دوال أخرى، [ 8 ] كما هو موضح في المثال التالي:

دالة المُجمِّع ( n : عدد صحيح ) -> دالة قابلة للاستدعاء [[ عدد صحيح عدد صحيح ]: دالة الزيادة ( x : عدد صحيح ) -> عدد صحيح : غير محلية n : عدد صحيح n += x إرجاع n إرجاع الزيادة

في لغة روبي

في لغة روبي ، يمكن اعتبار العديد من الكائنات كائنات دوال، وخاصةً كائنات Method وProc. كما تحتوي روبي على نوعين من الكائنات التي يمكن اعتبارها كائنات شبه دوال: UnboundMethod وblock. يجب ربط UnboundMethod بكائن أولاً (ليصبح بذلك Method) قبل استخدامه ككائن دالة. يمكن استدعاء Blocks ككائنات دوال، ولكن لاستخدامه بأي صفة أخرى ككائن (مثل تمريره كوسيط)، يجب تحويله أولاً إلى Proc. ومؤخراً، :أصبح بالإمكان أيضاً تحويل الرموز (التي يتم الوصول إليها عبر المؤشر الأحادي الحرفي) إلى s. باستخدام عامل Procروبي الأحادي - المكافئ لاستدعاء كائن، بافتراض وجود هذا الأسلوب - ابتكر مشروع Ruby Extensions حلاً بسيطاً.&to_proc

class Symbol def to_proc proc { | obj , * args | obj . send ( self , * args ) } end end

الآن، fooيمكن أن يكون الأسلوب كائن دالة، أي Proc، عبر &:fooويتم استخدامه عبر takes_a_functor(&:foo). Symbol.to_procتمت إضافته رسميًا إلى Ruby في 11 يونيو 2006، خلال RubyKaigi2006.

نظراً لتعدد أشكالها، لا يُستخدم مصطلح "Functor" في لغة روبي عموماً للدلالة على كائن دالة. إنما يُطلق اسم "Functor" على نوع من أنواع تفويض الإرسال الذي قدمه مشروع Ruby Facets . وأبسط تعريف له هو:

فئة Functor def تهيئة ( & func ) @func = func end def way_missing ( op , * args , & blk ) @func . استدعاء ( op , * args , & blk ) end end

هذا الاستخدام أقرب إلى الاستخدام المستخدم في لغات البرمجة الوظيفية، مثل ML ، والمصطلحات الرياضية الأصلية.

معانٍ أخرى

في سياق نظري أكثر، يمكن اعتبار كائن الدالة أي مثال من فئة الدوال، خاصة في لغات مثل Common Lisp حيث تكون الدوال كائنات من الدرجة الأولى .

تستخدم عائلة لغات البرمجة الوظيفية ML مصطلح "الدالة" لتمثيل عملية الربط بين الوحدات النمطية، أو بين الأنواع، وهي تقنية لإعادة استخدام الشيفرة. وتُشابه الدوال المستخدمة بهذه الطريقة المعنى الرياضي الأصلي للدالة في نظرية الفئات ، أو استخدام البرمجة العامة في لغات C++ أو Java أو Ada .

في لغة هاسكل ، يُستخدم مصطلح functor أيضًا لمفهوم مرتبط بمعنى functor في نظرية الفئات.

في لغة برولوج واللغات ذات الصلة، يعتبر مصطلح functor مرادفًا لرمز الدالة .

انظر أيضاً

ملحوظات

  1. في لغة C++، يُعرَّف الكائن الوظيفي (functionoid) بأنه كائن يحتوي على دالة رئيسية واحدة، بينما يُعدّ المُنتَج الوظيفي (functor) حالة خاصة من الكائن الوظيفي. [ 1 ] وهما متشابهان مع الكائن الوظيفي، لكنهما ليسا متطابقين .

مراجع

  1. ما الفرق بين الدالة الجزئية والدالة الوظيفية؟
  2. سيلان ليو. "دليل لغة C++ الجزء الأول - الأساسيات: 5.10 تُستخدم مؤشرات الدوال بشكل أساسي لتحقيق تقنية الاستدعاء، والتي سيتم شرحها لاحقًا" . TRIPOD: دروس البرمجة، حقوق النشر © سيلان ليو 2002. تاريخ الاسترجاع: 7 سبتمبر 2012. تُستخدم مؤشرات الدوال بشكل أساسي لتحقيق تقنية الاستدعاء، والتي سيتم شرحها لاحقًا.
  3. باويل تورليسكي (2009-10-02). "دليل لغة C++ الجزء الأول - الأساسيات: 5.10 تُستخدم مؤشرات الدوال بشكل أساسي لتحقيق تقنية الاستدعاء، والتي سنناقشها لاحقًا" . بضعة أسطر فقط . تم الاطلاع عليه في 2012-09-07 . قدمت PHP 5.3، إلى جانب العديد من الميزات الأخرى، ميزة الإغلاقات. لذا، يمكننا الآن أخيرًا القيام بكل الأشياء الرائعة التي يستطيع مطورو Ruby / Groovy / Scala / أي لغة برمجة حديثة القيام بها، أليس كذلك؟ حسنًا، يمكننا ذلك، لكننا على الأرجح لن نفعل... إليكم السبب.
  4. "حل التحميل الزائد § استدعاء كائن فئة" . cppreference.com .
  5. مولد البطاريات
  6. توثيق PHP حول الدوال السحرية
  7. مولد البطاريات
  8. دليل مرجعي للغة بايثون - تعريفات الدوال

للمزيد من القراءة

  • ديفيد فانديفورد ونيكولاي إم جوسوتيس (2006). قوالب C++: الدليل الكامل ، رقم ISBN 0-201-73484-2على وجه التحديد، تم تخصيص الفصل 22 للكائنات الوظيفية.