المعلمة الإجرائية
في مجال الحوسبة ، المعامل الإجرائي هو معامل خاص بإجراء هو نفسه إجراء . [ 1 ]
يُعد هذا المفهوم أداة برمجة قوية ومتعددة الاستخدامات للغاية ، لأنه يسمح للمبرمجين بتعديل خطوات معينة من إجراء المكتبة بطرق معقدة بشكل تعسفي، دون الحاجة إلى فهم أو تعديل رمز هذا الإجراء.
تُعدّ هذه الأداة فعّالة ومريحة للغاية في اللغات التي تدعم تعريفات الدوال المحلية ، مثل باسكال ولهجة جنو الحديثة للغة سي . [ 2 ] وتزداد فعاليتها عند توفر إغلاقات الدوال . وتُوفّر الكائنات في لغات البرمجة كائنية التوجه نفس الوظائف (وأكثر) ، ولكن بتكلفة أعلى بكثير.
ترتبط المعاملات الإجرائية إلى حد ما بمفهومي الدالة من الدرجة الأولى والدالة المجهولة ، لكنها تختلف عنهما. يتعلق هذان المفهومان بكيفية تعريف الدوال أكثر من كيفية استخدامها.
المفهوم الأساسي
في معظم اللغات التي توفر هذه الميزة، يمكن استدعاء المعامل الإجرائي f الخاص بالروتين الفرعي P داخل جسم P كما لو كان إجراءً عاديًا:
الإجراء P ( f ): إرجاع f (6,3) * f (2,1)
عند استدعاء الدالة الفرعية P ، يجب تمرير وسيط واحد إليها، ويجب أن يكون هذا الوسيط دالة مُعرَّفة مسبقًا ومتوافقة مع طريقة استخدام P للمعامل f . على سبيل المثال، إذا قمنا بتعريف
الإجراء plus ( x , y ): إرجاع x + y
عندئذٍ يمكننا تسمية P ( زائد )، وستكون النتيجة زائد (6،3) * زائد (2،1) = (6 + 3)*(2 + 1) = 27. من ناحية أخرى، إذا عرّفنا
الإجراء quot ( u , v ): إرجاع u / v
عندئذٍ، ستُعيد الدالة P ( quot ) القيمة quot (6,3) * quot (2,1) = (6/3) * (2/1) = 4. وأخيرًا، إذا عرّفنا
الإجراء evil ( z ) يُرجع z + 100
عندها لن يكون لاستدعاء P ( الشر ) معنى كبير، وقد يتم اعتباره خطأً.
التفاصيل النحوية
قد تسمح بعض لغات البرمجة التي تتمتع بهذه الميزة، أو تشترط، تعريفًا كاملاً لنوع كل مُعامل إجرائي f ، بما في ذلك عدد ونوع وسائطه، ونوع نتيجته، إن وُجدت. على سبيل المثال، في لغة البرمجة C، يمكن كتابة المثال أعلاه على النحو التالي:
int P ( int ( * f )( int a , int b )) { return f ( 6 , 3 ) * f ( 2 , 1 ); }من حيث المبدأ، يجب أن تكون الدالة الفعلية actf، التي تُمرر كوسيط عند استدعاء P ، متوافقة نوعيًا مع النوع المُعلن لمعامل الإجراء f . وهذا يعني عادةً أن actf و f يجب أن تُرجعا نفس نوع النتيجة، وأن يكون لهما نفس عدد الوسائط، وأن تكون الوسائط المتناظرة من نفس النوع. مع ذلك، ليس من الضروري أن تكون أسماء الوسائط متطابقة، كما يتضح من أمثلة علامتي الجمع والطرح أعلاه. ومع ذلك، قد تكون بعض لغات البرمجة أكثر تقييدًا أو أكثر تساهلًا في هذا الصدد.
نطاق
في اللغات التي تسمح بالمعاملات الإجرائية، تُعرَّف قواعد النطاق عادةً بحيث تُنفَّذ المعاملات الإجرائية في نطاقها الأصلي. بتعبير أدق، لنفترض أن الدالة actf تُمرَّر كوسيط إلى الدالة P ، كمعاملها الإجرائي f ؛ ثم تُستدعى f من داخل جسم الدالة P. أثناء تنفيذ actf ، فإنها ترى بيئة تعريفها.
إن تطبيق قواعد تحديد النطاق هذه ليس بالأمر الهين. فبحلول وقت تنفيذ actf ، قد تكون سجلات التنشيط التي تحتوي على متغيرات بيئتها موجودة في عمق كبير من مكدس الاستدعاءات. هذه هي ما تُعرف بمشكلة الوسيطة الهابطة .
مثال: فرز الإدراج العام
يُمكن شرح مفهوم المعامل الإجرائي بشكل أفضل من خلال الأمثلة. ومن التطبيقات النموذجية لهذا المفهوم التنفيذ العام التالي لخوارزمية فرز الإدراج ، والذي يأخذ معاملين صحيحين a و b ومعاملين إجرائيين prec و swap :
الإجراء isort ( a , b , prec , swap ): عدد صحيح i , j ; i ← a ; بينما i ≤ b نفّذ j ← i ; بينما j > a و prec ( j , j −1) نفّذ swap ( j , j −1); j ← j −1; i ← i +1;
يمكن استخدام هذه العملية لترتيب عناصر المصفوفة x ، من أي نوع، من x [ a ] إلى x [ b ]، وفقًا لترتيب يحدده المستخدم. يجب أن يكون المعاملان prec و swap دالتين ، يُعرّفهما المستخدم ، تأخذ كل منهما عددين صحيحين r و s بين a و b . يجب أن تُرجع الدالة prec القيمة true إذا وفقط إذا كان يجب أن تسبق البيانات المخزنة في x [ r ] البيانات المخزنة في x [ s ]، وفقًا للترتيب الذي حدده المستخدم. أما الدالة swap ، فيجب أن تُبدّل محتويات x [ r ] و x [ s ]، ولا تُرجع أي نتيجة.
باختيار الدالتين prec و swap بشكل مناسب، يمكن استخدام إجراء isort نفسه لإعادة ترتيب المصفوفات من أي نوع بيانات، مخزنة في أي وسيط ومُنظمة في أي بنية بيانات توفر وصولاً مُفهرسًا إلى عناصر المصفوفة الفردية. (مع ذلك، تجدر الإشارة إلى وجود خوارزميات فرز أكثر كفاءة من فرز الإدراج للمصفوفات الكبيرة).
فرز الأرقام العشرية
على سبيل المثال، يمكننا فرز مصفوفة z مكونة من 20 عددًا عشريًا، من z [1] إلى z [20]، بترتيب تصاعدي عن طريق استدعاء الدالة isort (1, 20, zprec , zswap )، حيث تُعرَّف الدالتان zprec و zswap على النحو التالي:
الإجراء zprec ( r , s ): إرجاع ( z [ r ] < z [ s ]); الإجراء zswap ( r , s ): float t ; t ← z [ r ]; z [ r ] ← z [ s ]; z [ s ] ← t
فرز صفوف المصفوفة
كمثال آخر، لنفترض أن M هي مصفوفة من الأعداد الصحيحة تحتوي على 10 صفوف و20 عمودًا، وتبدأ الفهارس من 1. سيقوم الكود التالي بإعادة ترتيب العناصر في كل صف بحيث تأتي جميع القيم الزوجية قبل جميع القيم الفردية:
الإجراء eoprec ( r , s ): return ( M [ i , r ] mod 2) < ( M [ i , s ] mod 2 ) ;الإجراء eoswap ( r , s ): عدد صحيح t ; t ← M [ i , r ]; M [ i , r ] ← M [ i , s ]; M [ i , s ] ← t ; for i from 1 to 10 do isort (1, 20, eoprec, eoswap);
لاحظ أن تأثيرات eoprec و eoswap تعتمد على رقم الصف i ، لكن إجراء isort لا يحتاج إلى معرفة ذلك.
إجراء فرز المتجهات
يستخدم المثال التالي الدالة isort لتعريف إجراء vecsort الذي يأخذ عددًا صحيحًا n ومتجهًا صحيحًا v بعناصر من v [0] إلى v [ n -1] ويقوم بترتيبها إما بترتيب تصاعدي أو تنازلي، اعتمادًا على ما إذا كانت المعلمة الثالثة incr صحيحة أم خاطئة ، على التوالي:
الإجراء vecsort ( n , v , incr ): الإجراء vprec ( r , s ): إذا كان incr، فأرجع v [ r ] < v [ s ]؛ وإلا فأرجع v [ r ] > v [ s ]؛الإجراء vswap ( r , s ): عدد صحيح t ; t ← v [ r ]; v [ r ] ← v [ s ]; v [ s ] ← tisort (0, n −1, vprec , vswap );
لاحظ استخدام تعريفات الدوال المتداخلة للحصول على دالة vprec التي يعتمد تأثيرها على المعامل incr المُمرر إلى vecsort . في اللغات التي لا تسمح بتعريفات الدوال المتداخلة، مثل لغة C القياسية، يتطلب الحصول على هذا التأثير كتابة كود معقد و/أو غير آمن للاستخدام في بيئات متعددة الخيوط .
مثال: دمج سلسلتين
يوضح المثال التالي استخدام المعاملات الإجرائية لمعالجة هياكل البيانات المجردة بشكل مستقل عن تنفيذها الفعلي. تكمن المشكلة في دمج سلسلتين مرتبتين من السجلات في سلسلة واحدة مرتبة، حيث يمكن للعميل اختيار طبيعة السجلات ومعيار الترتيب. يفترض التنفيذ التالي فقط إمكانية الإشارة إلى كل سجل بواسطة عنوان ذاكرة ، ووجود "عنوان فارغ" Λ لا يمثل عنوان أي سجل صالح. يجب على العميل توفير العنوانين A و B لأول سجلين في كل سلسلة، بالإضافة إلى الدوال prec و next و append ، والتي سيتم شرحها لاحقًا.
إجراء دمج ( A ، B ، prec ، nextA ، appendA ، nextB ، appendB ): عنوان ini ، fin ، t ini ← Λ؛ fin ← Λ طالما A ≠ Λ أو B ≠ Λ ، إذا كان B = Λ أو ( A ≠ Λ و B ≠ Λ و prec ( A ، B )) ، فإن t ← nextA ( A ) وfin ← appendA( A ، fin )؛ إذا كان ini = Λ، فإن ini ← fin و A ← t، وإلا فإن t ← nextB ( B ) وfin ← appendB ( B ، fin )؛ إذا كان ini = Λ، فإن ini ← fin و B ← t، أرجع ini
يجب أن تأخذ الدالة prec عنواني r و s لسجلين، واحد من كل تسلسل، وتعيد القيمة true إذا كان يجب أن يسبق السجل الأول الآخر في تسلسل الإخراج. يجب أن تأخذ الدالة nextA عنوان سجل من التسلسل الأول، وتعيد عنوان السجل التالي في نفس التسلسل، أو Λ إذا لم يكن هناك سجل تالٍ. يجب أن تضيف الدالة appendA السجل الأول من التسلسل A إلى تسلسل الإخراج؛ وسيطاتها هي عنوان A للسجل المراد إضافته، وعنوان fin للسجل الأخير في قائمة الإخراج (أو Λ إذا كانت القائمة لا تزال فارغة). يجب أن تُعيد الدالة appendA العنوان المُحدَّث للعنصر الأخير في قائمة الإخراج. أما الدالتان nextB و appendB فهما مماثلتان لتسلسل الإدخال الآخر.
دمج القوائم المرتبطة
لتوضيح استخدام إجراء الدمج العام، إليك الكود الخاص بدمج قائمتين مرتبطتين بسيطتين ، بدءًا من العقد الموجودة في العنوانين R و S. نفترض هنا أن كل سجل x يحتوي على حقل عدد صحيح x.INFO وحقل عنوان x.NEXT يشير إلى العقدة التالية ؛ حيث تكون حقول المعلومات مرتبة تصاعديًا في كل قائمة. يتم تفكيك قوائم الإدخال بواسطة عملية الدمج ، وتُستخدم عقدها لبناء قائمة الإخراج.
إجراء دمج القائمة ( R ، S ): الإجراء prec ( r , s ): إرجاع r . INFO < s . INFOالإجراء التالي ( x ) : إرجاع x.NEXTإجراء append ( x , fin ) إذا كان fin ≠ Λ ثم fin . NEXT ← x x . NEXT ← Λ إرجاع xإرجاع دمج ( R ، S ، prec ، next ، append ، next ، append )
دمج المتجهات
يوضح الكود التالي استقلالية إجراء الدمج العام عن التمثيل الفعلي للمتتاليات. فهو يدمج عناصر مصفوفتين عاديتين ، U [0] إلى U [ m -1] و V [0] إلى V [ n -1]، من أعداد عشرية، بترتيب تنازلي. لا يتم تعديل مصفوفات الإدخال، ويتم تخزين متتالية القيم المدمجة في متجه ثالث، W [0] إلى W [ m + n -1]. كما هو الحال في لغة البرمجة C، نفترض أن التعبير "& V " يُعطي عنوان المتغير V ، وأن "* p " يُعطي المتغير الذي عنوانه هو قيمة p ، وأن "&( X [ i ])" يُكافئ "&( X [0]) + i " لأي مصفوفة X وأي عدد صحيح i .
إجراء دمج المصفوفات ( U ، m ، V ، n ، W ): الإجراء prec ( r , s ): إرجاع (* r ) > (* s ) الإجراء nextU ( x ): إذا كان x = &( U [ m -1]) ، فأرجع Λ، وإلا فأرجع x + 1 الإجراء nextV ( x ): إذا كان x = &( V [ n -1]) ، فأرجع Λ، وإلا فأرجع x + 1 الإجراء append ( x , fin ) إذا كان fin = Λ ثم fin ← &( W [0]) (* fin ) ← (* x ) return fin + 1 إذا كانت m = 0 فإن U ← Λ، وإذا كانت n = 0 فإن V ← Λ . أرجع دالة الدمج ( U , V , prec , nextU , append , nextV , append ).
مثال: التكامل المحدد
التكامل على فترة زمنية
الإجراء التالي يحسب التكامل التقريبي∫ f ( x ) dx دالة حقيقية معطاة f على فترة معينة [ a , b ] من خط الأعداد الحقيقية . الطريقة العددية المستخدمة هي قاعدة شبه المنحرف بعدد خطوات n ؛ ويتم تقريب الأعداد الحقيقية بأعداد عشرية.
الإجراء Intg ( f , a , b , n ): float t , x , s ; integer i if b = a then return 0 x ← a ; s ← f ( a ) / 2; for i from 1 to n −1 do t ← i /( n +1); x ← (1− t ) * a + t * b ; s ← s + f ( x ) s ← f ( b ) / 2 return ( b − a ) * s / n
التكامل عبر القرص
لننظر الآن في مشكلة تكامل دالة معينة، مع وسيطين، على قرصمع المركز المحدد () ونصف القطر المعطىيمكن اختزال هذه المسألة إلى تكاملين متداخلين بمتغير واحد عن طريق تغيير المتغيرات.
الكود التالي ينفذ الصيغة الموجودة على الجانب الأيمن :
الإجراء DiskIntg ( g , xc , yc , R , n ) الإجراء gring ( z ): الإجراء gpolar ( t ): float x , y x ← xc + z * cos ( t ) y ← yc + z * sin ( t ) return g ( x , y ) عدد صحيح م ← مستدير ( n * z / R ) إرجاع z * Intg ( gpolar , 0, 2*π, m ) إرجاع Intg ( gring , 0, R , n )
يستخدم هذا الكود إجراء التكامل Intg على مستويين. يستخدم المستوى الخارجي (السطر الأخير) إجراء التكامل Intg لحساب تكامللتتراوح من 0 إلىيُحدد المستوى الداخلي (السطر قبل الأخير)باعتبارها التكامل الخطي لـفوق الدائرة ذات المركزونصف القطر.
تاريخ
تم اختراع المعلمات الإجرائية قبل عصر الحواسيب الإلكترونية، من قبل عالم الرياضيات ألونسو تشيرش ، كجزء من نموذج حساب التفاضل والتكامل لامدا الخاص به.
تم تقديم المعاملات الإجرائية كميزة في لغة البرمجة ALGOL 60. في الواقع، كان لدى ALGOL 60 آلية قوية لتمرير المعاملات " بالاسم " والتي يمكن أن تبسط بعض استخدامات المعاملات الإجرائية؛ انظر جهاز جينسن .
كانت المعاملات الإجرائية سمة أساسية في لغة البرمجة LISP ، التي قدمت أيضًا مفهوم إغلاق الدالة أو funarg . تسمح لغة البرمجة C بتمرير مؤشرات الدوال كمعاملات ، وهو ما يحقق نفس الغاية، وغالبًا ما تُستخدم كدوال رد نداء في البرمجة الموجهة بالأحداث وكمعالجات للأخطاء. مع ذلك، لا تسمح سوى قلة من مُجمِّعات C الحديثة بتعريفات الدوال المتداخلة، لذا فإن استخداماتها الأخرى نادرة نسبيًا. وُفِّرت المعاملات الإجرائية أيضًا في لغة باسكال، إلى جانب تعريفات الإجراءات المتداخلة؛ ولكن نظرًا لأن باسكال القياسية لم تسمح بالتجميع المنفصل، فقد استُخدمت هذه الميزة بشكل محدود في تلك اللغة أيضًا.
انظر أيضاً
مراجع
- ↑ غونزاليس، تيوفيلو؛ دياز-هيريرا، خورخي؛ تاكر، ألين (2014-05-07). دليل الحوسبة، الطبعة الثالثة: علوم الحاسوب وهندسة البرمجيات . مطبعة سي آر سي. رقم ISBN 978-1-4398-9852-9تم الاطلاع عليه بتاريخ 2025-09-01 .
- ↑ ويلسون/أديمان (29-06-2013). مقدمة عملية إلى لغة باسكال . سبرينغر ساينس آند بيزنس ميديا. ص 111. ISBN 978-1-4757-1764-8تم الاطلاع عليه بتاريخ 2025-09-01 .
- البرامج الفرعية
