تقنية الـcallable وقابلية الاستدعاء في محرك Godot

GodotGDscript

في هذه المقالة، سأشرح مفهوم قابلية الاستدعاء-Callable في لغة GDScript وكيفية التعامل واستخدام الدوال بطرق متقدمة تُسهِّل عليك اختصار وتنظيم أكوادك بشكل سلس وتمنح منظورًا جديدًا في التعامل  مع الدوال.

على سبيل المثال هل فكرت أن تخزن دالة معينة داخل متغير معين ؟
ثم تستخدم هذا المتغير لتمرره او ترسله لمكان آخر وتستدعي الدالة عن طريق هذا المتغير ؟
قد يكون الكلام غامضًا قليلًا لكن موضوعنا سيكون عن قابلية الاستدعاء وهو الذي يُساعد في هذه الأمور.

ما هو الـ Callable

قابلية الاستدعاء-Callable في لغة GDScript هو صنف مدمج (Built-in class) يمثل نوعًا خاصًا من البيانات، ويستخدم لتمثيل الدوال ككائنات. بمعنى أن قابلية الاستدعاء يمكن تمثيل الدوال و استدعائها واستخدامها بشكل مماثل لكيفية تعاملك مع البيانات والمتغيرات الأخرى في اللغة.

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

func sum(x, y):
    return x + y

var f: Callable = sum
# or var f = Callable(self, "sum")

لاحظ هنا قمت بتعريف دالة الجمع sum التي تقوم بجمع قيمتين وإرجاع الناتج، ومن ثم عرفت كائن f من نوع Callable وتم تعيينه ليكون مساويًا للدالة الجمع، بعد هذه الخطوة، أصبح بإمكانك استخدام الكائن f لاستدعاء الدالة الجمع.

قد تظن الآن أن f أصبحت دالة مماثلة لدالة الجمع وتستطيع أن تستدعيها هكذا f(1, 2) مثل ما تستدعي دالة الجمع هكذا sum(1, 2) بشكل مباشر.

var f : Callable = sum
var result = f(1, 2) # Error!!
print(result)

لكن في حقيقة الأمر، هذا ليس صحيحًا. تذكر أنني قلت أن f في الواقع تكون كائنًا من نوع الصنف “قابل الاستدعاء” وليست دالة بحد ذاتها، لذا لا يمكننا معاملتها كدالة بشكل مباشر، بل نعاملها ككائن يحتفظ أو مرتبط بالدالة.

وهذا هو سبب الخطأ الذي ظهر في الكود عند محاولة استخدام f بهذا الشكل f(1, 2).

استدعاء الدالة من خلال الـ Callable

تذكر في مقالة الاصناف ومقالة الـ class_name قلت أن كل كائن يحتوي بداخله على دوال وخواص خاصة بالصنف بالتالي لو كان لديك كائن من نوع “قابل للاستدعاء” لنسميهobj وأسندت له دالة الجمع،الكائن obj لديه الآن بداخله خواص ودوال خاصة بالصنف “قابل للاستدعاء” من ضمن تلك الدوال دالة تدعى call أي استدعاء.

 الآن لكي أقوم باستدعاء دالة الجمع المتواجدة داخل obj سأستخدم هذه الدالة call

var obj : Callable = sum
var result = obj.call(1, 2)
print(result) # 3

هنا عندما  استدعي obj.call(1, 2) فكأنني استدعى sum(1, 2)

تمرير دالة داخل دالة أخرى

من خلال استخدام قابلية الاستدعاء، يُمكنك الآن تمرير الدوال داخل دوال أخرى واستدعائها بسهولة.

func receive(obj : Callable):
    var result = obj.call(10, 5)
    print(result)

receive(sum) # 15

ستلاحظ أن دالة الاستقبال receive التى أنشأناها تستقبل المعامل obj الذي هو كائن من نوع callable، ثم تستدعيه وتعطيه قيم 5 و 10.

الآن، باستخدام هذه الدالة يمكنك  إرسال دالة الجمع دون استدعاؤها، بهذا الشكل receive(sum) أرسلت فقط الدالة. ما الذي تتوقعه أنت أن يحدث الآن ؟ أريدك أن تتبع الدالة وترى.

عند تمرير الدالة الجمع داخل الدالة الاستقبال سيتم تخزين الدالة الجمع  في المعامل obj الذي هو من نوع “قابل الاستدعاء”.
الآن أصبح لدينا كائن يدعى obj كمعامل داخل دالة الاستقبال يمثل دالة الجمع. هذا يعني أنك تستطيع تمرير دالة داخل دالة أخرى تمامًا كما ترسل المتغيرات داخل الدوال الأخرى.

داخل دالة الاستقبال لكي تستدعي sum عن طريق الكائن obj استخدم obj.call() وهكذا سيتم استدعاء الدالة التي يمثلها الكائن obj. عند استدعائها، يتوجب عليك أيضًا تزويدها بالقيم التى تحتاجها دالة الجمع.

فكما هو ملاحظ أن obj.call() هنا تحتاج لقيمتين، وذلك لأن دالة الجمع كانت تستقبل قيمتين عند استدعائها، لذا أرسلنا لـ obj.call() القيمتين x و y هكذا obj.call(5, 10).

وبهذا الشكل، أمكنَك استخدام واستدعاء دالة الجمع من خلال obj.call()، ثم تم استقبال الناتج في متغير النتيجة result والذي هو ناتج جمع القيمتين 15، هكذا var result = obj.call(5, 10) وأخيرًا تم طباعة النتيجة باستخدام print(result).

لاحظ كيف بسهولة استطعت أن تمرر دالة داخل دالة بفضل قابلية الاستدعاء.

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

توضيح فكرة استخدام

في هذا الجزء، أطبق مثالًا أخيرًا لكي تفهم فكرة قابلية الاستدعاء بشكل نهائي.

func sum(x, y):
    return x + y
func applyOperation(x, y, obj: Callable):
    var result = obj.call(x, y)
    return result

هنا توجد دالة applyOperation(x, y, obj) التي تستقبل القيم x ,y التي ستُرسل للمعامل obj الذي سيستدعى call ويرسل تلك القيم ثم ترجع الناتج ببساطة.

المثال بسيط لكن كيف ستستفيد من هذا ؟

var result = applyOperation(5, 10, sum)
print(result) # return 15

لاحظ  انني ارسلت sum لدالة تنفيذ الإجراء-applyOperation ليستقبلها المعامل obj الذي يمثل كائن من صنف “قابل للاستدعاء” كما تعرف.
ثم داخل دالة applyOperation ستجد var result = obj.call(x, y) الكائن obj الآن يمثل دالة الجمع التي أرسلتها ودالة الجمع كانت تستقبل قيمتين لذا obj.call أصبحت كذلك.

هنا الكائن obj كما فهمت سيمثل كائن يحتفظ بالدالة الأصلية التي أُسندت لها وهي دالة الجمع sum،
و يجب أن تدرك أن الكائن obj يمثل أي دالة ترسلها، فالأمر لا يختصر على الجمع.

بمعنى أنني أستطيع الآن استخدام أكثر من دالة كـ “قابل للاستدعاء”، كيف ذلك ؟ ركز معي هنا جيدًا.
لنفترض أن لدينا دوالًا إضافية بجانب دالة الجمع.

func sum(x, y):
    return x + y
func sub(x, y):
    return x - y
func mul(x, y):
    return x * y
func div(x, y):
    return x / y

هنا لدي مجموعة من الدوال، والآن يمكنني أن أستخدم أي منها كـ قابلية استدعاء دالة تنفيذ الإجراء.

func applyOperation(x, y, obj):
    var result = obj.call(x, y) # استدعاء الدالة التي مررتها
    return result

# يمكنك الآن استدعاء دالة تنفيذ الإجراء مع تمرير دوال قابلية الاستدعاء متنوعة :
applyOperation(5, 10, sum); # 15
applyOperation(5, 10, sub); # -5
applyOperation(5, 10, mul); # 50
applyOperation(5, 10, div); # 0.5  

هنا أُرسل دوالًا مختلفة قابلة للاستدعاء داخل دالة تنفيذ الأجراء في كل مرة.

ستجد أن دالة تنفيذ الاجراء تستقبل دوال قابلة للاستدعاء متنوعة بسهولة، وتُمثل الدوال المرسلة وتُستدعى بنفس الطريقة. يمكِنك الآن استخدام مجموعة متنوعة من الدوال بشكل قابل لاستدعاءها داخل دالة تنفيذ الإجراء والحصول على ناتج متنوعة بحسب وظيفة الدالة المرسلة بسهولة.

الآن، أرجو أن تكون فهمت كيفية استخدام قابلية الاستدعاء لتمرير دوال داخل دوال أخرى بطريقة مرنة وتحقيق تنظيم أكبر وإعادة استخدام الأكواد بكفاءة. 

تعتبر فكرة قابلية الاستدعاء-callable أحد المفاهيم المهمة في برمجة GDScript والتي توفر ميزات قوية للتحكم في الدوال وتطبيق نمط البرمجة الدالية بشكل فعال والذي سيمهد لمفاهيم أخرى كـ lambda التي سأخصص لها مقالة خاصة لها بإذن الله تعالى.

للمزيد من المعلومات :
https://docs.godotengine.org/en/stable/classes/class_callable.html
https://tabarani.tk/articles/callback

0 0 votes
Article Rating
Subscribe
نبّهني عن
guest

0 تعليقات
Inline Feedbacks
View all comments