استدعاء التوابع في روبي

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

هذا القسم يغطّي فقط كيفيّة استدعاء التوابع، وستُشرَح كيفيّة تعريف التّوابع في قسم آخر.

المستقبِل

المستقبِل (Receiver) الافتراضي في لغة روبي هو self وهو الذي يُستخدَم في حال عدم تحديد أيّ مستقبل آخر. ولأجل تحديد مستقبِل، يستَدعى التابع بالشّكل التالي:
my_object.my_method
في هذه الحالة تُرسل رسالة my_method إلى الكائن my_object. فكلّ كائن يمكنه أن يكون مستقبِلًا لتابع ما؛ لكن حتى يتمّ ذلك، يجب أن يكون التابع مرئيًّا لهذا الكائن، وإلّا فسيظهر خطأ من نوع NoMethodError

يمكنك استخدام المعامل .& (متبوعًا بنقطة) لتعيّن & كمستقبل، حينها لن يُنفّذ التابع my_method، وستكون النتيجة nil عندما يكون المستقبِل nil، ولن تُقيَّم معامِلات التّابع my_method في هذه الحالة.

كما يمكنك استخدام :: لتعيين المستقبل، لكنّ هذه الطريقة نادرة الاستخدام لأنّها قد تسبّب التباسًا مع الرمز :: المستخدم في مجالات الأسماء (namespaces).

الوسائط

تدعم لغة روبي ثلاثة أنواع من الوسائط (arguments) التي يمكن تمريرها عند إرسال الرسائل وهي: الوسائط الموضعية، ووسائط القيم المفتاحية (أوالوسائط المسماة [named arguments])، ووسائط الكتلة البرمجية. كل رسالة تُرسَل تستخدم نوعًا أو اثنين أو كل هذه الأنواع، إلّا أنّها إذا استُخدمت معًا، فيجب أن تكون ضمن هذا الترتيب نفسه.

كلّ الوسائط في لغة روبي تُمرّر مرجعيًا ولا يؤجّل تقييمها. تفصل الوسائط بالفاصلة , بالشكل التالي:
my_method(1, '2', :three)
يُمكن أن تكون الوسائط على شكل تعبير برمجي كالجدول Hash:
'key' => value
أو وسائط مسماة (Keyword arguments):
key: value
الجداول Hash والوسائط المسماة يجب أن تكون متجاورة ويجب أن تتلو الوسائط الموضعيّة، ولا بأس في أن تُخلط فيما بينها:
my_method('a' => 1, b: 2, 'c' => 3)

الوسائط الموضعيّة

تتبع الوسائط الموضعيّة (Positional Arguments) للرسالة اسم التابع مباشرة:
my_method(argument1, argument2)
في كثير من الحالات، لا تكون الأقواس ضروريّة عند إرسال الرسالة:
my_method argument1, argument2
لكنّها ضروريّة لإزالة الالتباس، فالمثال التّالي سيسبب خطأً في صيغة (SyntaxError)  بسبب أنّ مفسّر لغة روبي لن يعرف إلى من يجب إرسال الوسيط الثالث argument3:
method_one argument1, method_two argument2, argument3
إذا احتوى تعريف التابع على وسيط مبدوء برمز *، فالوسائط الموضعيّة الإضافيّة ستُسنَد إلى هذا الوسيط في التابع على هيئة مصفوفة. إذا لم يحتوِ تعريف التابع على وسائط مسماة، فإنّ أيّة وسائط مسماة أو الوسائط ذات النوع Hash ستُسند جميعها كجدول Hash واحد إلى آخر وسيط في تعريف التابع:
def my_method(options)
 p options
end
my_method('a' => 1, b: 2) # تطبع: {'a'=>1, :b=>2}
إذا مُرر عدد كبير من الوسائط الموضعية، فسيسبب ذلك ظهور الخطأ ArgumentError.

الوسائط الموضعية الافتراضية

عندما تعرَّف وسائط افتراضية في تعريف التابع، فلست بحاجة إلى تزويده بكل الوسائط عند الاستدعاء، فالمفسّر سيملأ المعاملات الناقصة وفق الترتيب.

ففي أبسط الحالات، عندما تكون الوسائط الافتراضية هي الأخيرة في أقصى اليمين مثل:
def my_method(a, b, c = 3, d = 4)
 p [a, b, c, d]
end
لكلّ من المعاملين c و d قيمة افتراضية، فإذا مُرِّر معاملين فقط في استدعاء التابع مثل:
my_method(1, 2)
فستكون النتيجة كالتالي:
[1, 2, 3, 4] 
وإذا مرّرت ثلاثة معاملات:
my_method(1, 2, 5)
فستكون النتيجة:
[1, 2, 5, 4]
حيث يملأ مفسّر روبي المعاملات النّاقصة من اليسار إلى اليمين. تسمح لغة روبي بأن توضع الوسائط الافتراضية في المنتصف بين الوسائط الموضعية الأخرى. لنأخذ هذا المثال الأكثر تعقيدًا: 
def my_method(a, b = 2, c = 3, d)
 p [a, b, c, d]
end
هنا b و c يأخذان قيمًا افتراضيّة. فإذا مُرِّر معاملين فقط في استدعاء التابع بالشكل:
my_method(1, 4)
ستكون النتيجة:
[1, 2, 3, 4]
وإذا مُرِّر ثلاثة معاملات:
my_method(1, 5, 6)
ستكون النتيجة:
[1, 5, 3, 6]
من الصّعب وصف الذي يحصل هنا بالكلمات، لذلك سنشرحها باستخدام المتغيّرات وقيمها.

بداية تُسند القيمة 1 إلى a، ثمّ تسند القيمة 6 إلى d. وهكذا يبقى المعاملَين ذوَي القيمة الافتراضية. وطالما أنّ القيمة 5 لم تُسنَد بعد، فستعطى إلى b، ويحصل c على القيمة الافتراضية 3.

الوسائط المسماة

الوسائط المسماة تلي الوسائط الموضعية ويفصل بينها فواصل:
my_method(positional1, keyword1: value1, keyword2: value2)
إذا لم تظهر الوسائط المسماة عند استدعاء التابع رغم وجودها في تعريفه، فستأخذ القيمة الافتراضيّة المعرّفة فيه. أمّا إذا مُرّرت هذه الوسائط في الاستدعاء دون أن يحتوي عليها تعريف التّابع، فسيظهر حينئذ الخطأ ArgumentError.

وسائط الكتل البرمجية

وسائط الكتل البرمجيّة تمرّر كتلة برمجيّة من النّطاق الذي يستدعي التّابع، ويكون ترتيب هذه الوسائط دائمًا في نهاية الوسائط الممرّرة للتابع. وتُمرّر الكتلة البرمجيّة باستخدام do.. end أو الأقواس {..}:
my_method do
 # ...
end
أو:
my_method {
 # ...
}
تأخذ do-end أولويّة أدنى من القوسين {} المعقوصين؛ ففي المثال التّالي:
method_1 method_2 {
 # ...
}
تُرسل الكتلة البرمجيّة إلى التابع method_2؛ بينما في المثال:
method_1 method_2 do
 # ...
end
ترسل الكتلة إلى التابع method_1. يمكن أن تستقبل الكتلة البرمجية وسائط من التابع المرسلة له. هذه الوسائط تعرّف بطريقة مشابهة لطريقة تعريفها في التوابع، وتمرّر ضمن |...| بعد كلمة do أو القوس المفتتح للكتلة:
my_method do |argument1, argument2|
 # ...
end

الوسائط المحلية للكتلة البرمجية

يمكنك أيضًا تعريف وسائط محليّة على مستوى الكتلة البرمجيّة باستخدام الرّمز ; في قائمة وسائط الكتلة. لاحظ أنّ إسناد قيم إلى هذه الوسائط ليس له أيّ تأثير على المتغيّرات المحليّة التي لها نفس الاسم والموجودة خارج هذه الكتلة في النطاق الذي يستدعي التابع:
def my_method
 yield self
end
place = "world"
my_method do |obj; place|
 place = "block"
 puts "hello #{obj} this is #{place}"
end
puts "place is: #{place}"
وتكون النتيجة:
hello main this is block
place is world
فالمتغيّر place ضمن الكتلة الممرّرة إلى التابع ليس نفسه المتغيّر place خارجها، وبالمقابل لو حذفنا place ; من وسائط الكتلة، فستكون النتيجة:
hello main this is block
place is block

التحويل من مصفوفة إلى وسائط

ليكن لدينا هذا التابع:
def my_method(argument1, argument2, argument3)
end
يمكنك تحويل مصفوفة إلى سلسلة من الوسائط عن طريق تمرير المصفوفة مسبوقة برمز النجمة * (المسمى بمعامل الفصل):
arguments = [1, 2, 3]
my_method(*arguments)
أو:
arguments = [2, 3]
my_method(1, *arguments)
كلاهما مكافئ لما يلي:
my_method(1, 2, 3)
في حالة كان التابع يستقبل وسائط مسماة، فاستخدام معامل الفصل حينها سيحوّل الجدول Hash الموجود في نهاية المصفوفة إلى وسائط مسماة:
def my_method(a, b, c: 3)
end
arguments = [1, 2, { c: 4 }]
my_method(*arguments)
يمكنك أيضًا استخدام الرمز ** (سيأتي شرحه لاحقًا) لتحوّل عناصر الجدول Hash إلى وسائط مسماة.

إذا لم يطابق عددُ الكائنات في المصفوفة عددَ المعاملات في التابع، فسيظهر الخطأ ArgumentError.

وإذا جاء معامل الفصل في بداية الوسائط عند الاستدعاء، فيجب استخدام الأقواس عندها منعًا لظهور تحذير (warning).

التحويل من جدول Hash إلى وسائط مسماة

لنأخذ هذا التابع على سبيل المثال:
def my_method(first: 1, second: 2, third: 3)
end
يمكنك تحويل محتوى الجدول Hash إلى وسائط مسماة باستخدام المعامل **:
arguments = { first: 3, second: 4, third: 5 }
my_method(**arguments)
أو:
arguments = { first: 3, second: 4 }
my_method(third: 5, **arguments)
كلاهما مكافئ لما يلي:
my_method(first: 3, second: 4, third: 5)
استخدم المعامل ** في تعريف التابع لتجمّع الوسائط المسماة أيًا كانت، إذ أنّها لن تُجمّع باستخدام المعامل *:
def my_method(*a, **kw)
 p arguments: a, keywords: kw
end
my_method(1, 2, '3' => 4, five: 6)
وتكون النتيجة:
{:arguments=>[1, 2, {"3"=>4}], :keywords=>{:five=>6}}
على عكس معامل الفصل الموضّح سابقًا، فإنّ المعامل ** ليس له اسم معيّن متعارف عليه.

التحويل من الكائنات Proc إلى كتل برمجية

لنأخذ التابع التالي على سبيل المثال:
def my_method
 yield self
end
يمكنك تحويل كائن من النوع proc أو lambda إلى كتلة برمجيّة باستخدام المعامل &:
argument = proc { |a| puts "#{a.inspect} was yielded" }
my_method(&argument)
إذا سبق في الاستدعاء معامل الفصل *، فيجب حينها استخدام الاقواس منعًا لظهور تحذير.

وكما هو الحال بالنّسبة للمعامل **، فإنّ المعامل & أيضًا ليس له اسم معيّن متعارف عليه.

البحث عن التوابع

عندما ترسل رسالة، فإنّ مفسر روبي يبحث عن التابع الذي يطابق اسم مستقبل الرسالة؛ هذه التّوابع مخزّنة في أصناف (classes) ووحدات (modules) يمرّ المفسّر عليها أثناء عمليّة البحث، ولا يبحث في كائناتها.

هذا هو الترتيب المتّبع في عمليّة البحث عن تابع للصنف أو الوحدة  R الخاصّ بمستقبل معين:

  • وحدات R السابقة بترتيب عكسيّ.
  • تابع مطابق في R.
  • الوحدات المتضمّنة في R بترتيب عكسيّ.

وإذا كان للصنفّ R صنف أب (superclass)، فستكرّر العمليّة في الصنف الأب حتى يُعثَر على التابع، وتتوقّف عمليّة البحث متى عُثر على التابع.

وإذا لم يُعثر على أيّ نتيجة، فستعاد العمليّة من البداية لكن بالبحث عن method_missing، والتابع الافتراضيّ له BasicObject.method_missing والذي يسبّب ظهور الخطأ NameError عندما يستدعى.

عند تفعيل ميزة التحسينات الاختبارية، فسيتغيّر ترتيب البحث عن التوابع، اقرأ توثيق التحسينات لمزيد من التفاصيل.

مصادر