Ruby/calling methods

من موسوعة حسوب
مراجعة 21:32، 15 نوفمبر 2018 بواسطة نور-الدين-الهبل (نقاش | مساهمات) (نسخ الترجمة قبل تنسيقها)
(فرق) → مراجعة أقدم | المراجعة الحالية (فرق) | مراجعة أحدث ← (فرق)

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

عندما تستدعي تابعًا، فإنّك تمرّر رسالة لكائن معيّن لأجل تنفيذ مهمّة معيّنة، ويتمّ ذلك في لغة روبي كالتّالي:

my_method()

لاحظ أنّ استخدام الأقواس المنحنية هنا اختياريّ:

my_method

المعتمد في هذا التّوثيق أن تُستخدّم الأقواس عند وجود المعامِلات لإزالة الالتباس، إلا في حالة وجود فرق بين وجود الأقواس وحذفها.

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

المستقبِل

المستقبِل الافتراضي في لغة روبي هو self وهو الذي يُستخدَم في حال عدم تحديد أيّ مستقبل آخر. ولأجل تحديد مستقبِل يستَدعى التابع بالشّكل التالي:

my_object.my_method

في هذه الحالة تُرسل رسالة my_method إلى الكائن my_object. فكلّ كائن يمكنه أن يكون مستقبِلًا لتابع ما، لكن حتى يتمّ ذلك يجب أن يكون التابع مرئيّ  لهذا الكائن، وإلّا فسيظهر خطأ من نوع NoMethodError. 

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

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

المعاملات

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

كلّ المعاملات في لغة روبي تُمرّر مرجعيًا ولا يؤجّل تقييمها. وكلّ معامل يُكتب متبوعًا بفاصلة , كالتّالي:

my_method(1, '2', :three)

يُمكن أن تكون المعاملات على شكل تعبير برمجي كالمصنِّفات (Hashes):

'key' => value

أو معاملات القيم المفتاحية (Keyword arguments):

key: value

المصنِّفات ومعاملات القيم المفتاحية يجب أن تكون متجاورة ويجب أن تتلو المعاملات الموضعيّة، ولا بأس في أن تُخلط فيما بينها:

my_method('a' => 1, b: 2, 'c' => 3)

المعاملات الموضعيّة

المعاملات الموضعيّة للرسالة تتبع اسم التابع مباشرة:

my_method(argument1, argument2)

في كثير من الحالات لا تكون الأقواس ضروريّة عند إرسال الرسالة:

my_method argument1, argument2

لكنّها ضروريّة لإزالة الالتباس، فالمثال التّالي سيسبب خطأ صيغة (SyntaxError)  بسبب أنّ مفسّر اللّغة لن يعرف إلى من يجب إرسال المعامل الثالث argument3:

method_one argument1, method_two argument2, argument3

إذا احتوى تعريف التابع على معامل مبدوء برمز *، فالمعاملات الموضعيّة الإضافيّة ستُسند إلى هذا المعامل في التابع على هيئة مصفوفة.

إذا لم يحتوِ تعريف التابع على معاملات قيم مفتاحية فإنّ أيّة معاملات قيم مفتاحية أو معاملات مصنِّفات ستُسند جميعها كمصنِف واحد إلى آخر معامل في تعريف التابع:

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)

You will see ruby print [1, 2, 3, 4].

فستكون النتيجة كالتالي:

[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

or:

أو:

my_method {

 # ...

}

تأخذ do-end أولويّة أدنى من {}، ففي المثال التّالي:

method_1 method_2 {

 # ...

}

تُرسل الكتلة البرمجيّة إلى التابع method_2، بينما في المثال:

method_1 method_2 do

 # ...

end

ترسل الكتلة إلى التابع method_1. لاحظ أنّه في حال استخدمت الأقواس {} في الحالة الأولى فسترسل الكتلة إلى التابع الأول 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)

في حالة أنّ التابع يستقبل معاملات القيم المفتاحية فاستخدام معامل الفصل حينها سيحوّل المصنّف الموجود في نهاية المصفوفة إلى معاملات قيم مفتاحية:

def my_method(a, b, c: 3)

end

arguments = [1, 2, { c: 4 }]

my_method(*arguments)

يمكنك أيضًا استخدام الرمز ** (سيأتي شرحه لاحقًا) لتحوّل مصنّفًا إلى معامل قيم مفتاحية.

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

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

التحويل من تصنيف إلى معامل قيم مفتاحية

لنأخذ هذا التابع على سبيل المثال:

def my_method(first: 1, second: 2, third: 3)

end

يمكنك تحويل المصنّف إلى معاملات قيم مفتاحية باستخدام المعامل **:

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) عندما يستدعى.

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

مصادر