الدالة function كمواطن درجة أولى

خالد الشقروني   15 06 2024

في لغات البرمجة الدالية واللغات التي تدعم المفاهيم الدالية functional concepts عموما، تكون الدالّة أو ال Function مواطن درجة أولى (first class citizen)، ويقال عنها أيضا دالة مستوى أعلى higher-order function (HOF)

ما معنى أن تكون الدالة و غيرها مواطن درجة أولى وذات مستوى أعلى في لغات البرمجة؟

سنبدأ بالشرح العملي ؛ سوف نستخدم لغة F# لشرح المفاهيم الخاصة بالدالة، وقد اخترت هذه اللغة لبساطتها و وضوحها في تبيان المفاهيم التي سنتعرّف علها، مع هذا؛ قمت باعداد نسخ لهذه الأمثلة بلغات أخرى للمقارنة ولمزيد من الاستيعاب، تم إعدادها بلغة Python ولغة Go ولغة Rust. (تجدون الرابط لها أدناه).

لنبدأ،

الصيغة الأبسط لتعريف دالة function هي:

let add x y = 
   x + y

إسم الدالة هو add، وبها محددان parameters وهما x و y، وبعد علامة = يكون جسم الدالة وهو x + y ولأن x + y هو التعبير expression الوحيد والأخير في جسم الدالة، فسيكون ناتج هذا التعبير هي القيمة التي ترجعها الدالة.

في F# (والعديد من اللغات الأخرى) يمكن الاستغناء عن تحديد نوع البيانات صراحة explicitly وجعل اللغة تستنتج  أو تستدل inference على نوع البيانات ضمنيا implicitly ، لذلك إذا أردنا تحديد نوع البيانات صراحة في الدالة فستكون الدالة كالتالي:

let add (x: int)(y: int): int =
   x + y

لاحظ أننا لا نحتاج لوضع أمر return أو result حيث أن آخر تعبير في الدالة سيتم اعتباره قيمة الدالة المرتجعة ، وسنلاحظ نفس الأمر في لغة Rust.

مناداة هذه الدالة واستدعائها تتم بالتعبير التالي:

let a = add 1 1

على نفس المنوال، يمكننا أيضا صنع دالة أخرى ترجع حصيلة ضرب رقمين (سنحتاجها لاحقا):

let multiply x y =
   x * y 

1- تخصيص دالة لمعرّف (متغيّر)

في اللغات الدالية يمكن تخصيص معرّف identifier (أو متغيّر variable) للدالة، فنقول مثلا:

let my_add = add

هنا تم تخصيص الدالة للمعرّف my_add ، وبإمكان المعرّف أن يقوم مقام الدالة، ونتحصّل على نفس النتيجة.

let aa = my_add 1 1

الصيغة الأصلية للدالة:

صيغة الدالة التي رأيناها سابقا:

let add x y = x + y

 

هي صيغة مُحلاة syntactic sugar أي أنها صيغة محوّرة عن الصيغة الأصلية من أجل سهولة الاستخدام،  أما الصيغة الأصلية فهي:

let add = fun x y = x + y

add هنا سيكون معرّف (متغيّر) تم تخصيص قيمة له، القيمة هنا دالة ليس لها اسم في حد ذاتها، ويشار للدالة في لغة F# ب fun. بعض اللغات تشير إليها بكلمة lambda وأخرى تشير إليها بكلمة anonymous (بعض محررات الكود تكتب العلامة λ  بدلا من  lambda).

إذا دالة لامبدا أو الدالة عديمة الإسم anonymous هي هيكل لدالة بدون إسم.

ولأنها دالة مكتملة يمكننا مناداتها ككتلة واحدة مباشرة مثل:

let c = (fun x y = x + y) 3 3

والتي تعطينا ناتج جمع x و y أي 6.

2- تمرير دالة كمعطى argument

الدالة أيضا يمكن لها أن تستقبل دالة كمعطى argument فمثلا؛ الدالة التالية تستقبل ثلاث معطيات، الأول هو دالة، والثاني والثالث قيمتان سنفترض أنهما من نوع int (حسب مناداتها لاحقا)

let ApplyFunc f x y =
   f x y

حيث تقوم الدالة بتطبيق الدالة f التي استقبلتها على x و y أي : f(x,y)

ويمكن مناداتها كالتالي:

let d = ApplyFunc add 5 5

حيث مرّرنا لها الدالة add التي سبق تعريفها، فتقوم بتطبيق الدالة add على الرقمين 5 و 5 لتعطي الناتج 10. يمكن أيضا أن نمرر لها دالة أخرى، مثل multiply مع الرقمين 6 و 6 فتعطي لنا الناتج 36 :

let e = ApplyFunc multiply 6 6

يمكن أيضا تمرير هيكل دالة بالكامل بدلا من إسمها فقط مثل:

let f = ApplyFunc (fun x y = x * y + 1) 7 7

فتقوم بتطبيق الدالة التي استقبلتها على الرقمين 7 و 7، أي 7 × 7 ثم إضافة 1 ، لتعطي الناتج 50.

3- إرجاع دالة كقيمة

الدالة يمكن لها أن ترجع دالة جديدة كقيمة . فمثلا الدالة التالية تقوم باختبار المعطى x فإن كان يساوي a ؛ تردّ دالة تستقبل رقمين وترجع حاصل اضافتهما، غير ذلك تردّ دالّة غيرها تستقبل رقمين وترجع حاصل ضربهما:

let returnFunc x =
   if x == ‘a’ then
      fun x y = x + y
   else
      fun x y = x * y

ويمكن استدعاؤها كالتالي:

let myFun = returnFunc a
let g = myFun 8 6

الناتج سيكون 14

الخلاصة

تكون الدالة مواطن درجة أولى في اللغة البرمجية عندما يكون متاحا في اللغة:

وتكون الدالة عالية المستوى higher-order function في حالة إمكانية القيام بالنقطتين الأخيرتين.

كل الأمثلة الوارد أعلاه موجودة في برنامج F# قابل للتشغيل. كما توجد نسخ أخرى للبرنامج قابلة للتنفيذ والتشغيل بلغات Go و Rust و Python

روابط ملفات البرنامج في:

الملفات المصدرية للمشروع