التوابع في روبي

من موسوعة حسوب

تتضمّن التوابع في لغة روبي الوظائف التي يقوم بها برنامجك. إليك هذا المثال لتعريف تابع بسيط:

def one_plus_one
 1 + 1
end 

تعريف التابع يتكوّن من الكلمة المحجوزة def يتبعها اسم التابع، ثمّ جسم التابع، فالقيمة المعادة وفي النهاية الكلمة المحجوزة end. فعند تنفيذ التابع في المثال السابق، ستُعاد القيمة 2. هذا القسم سيغطّي تعريف التّوابع. ارجع إلى توثيق استدعاء التوابع لتتعرف على الصيغ المستخدمة لذلك الغرض.

تسمية التوابع

يمكن أن تستخدم لاسم التابع أحد المعاملات، وإلا فعليك أن تبتدئه بحرف أبجديّ أو أيّ محرف من محارف لوحة المفاتيح (أيضًا تعرف باسم محارف البتات الثمانية أي كل محرف بحجم 8 بت). ويمكن للاسم أن يتضمن أحرفًا وأرقامًا وشرطة سفليّة (_)، أو أيّ محرف من محارف البتات الثمانية. ومن المعتاد أن تستخدم الشرطة السفليّة لتفصل بين الكلمات في أسماء التوابع التي تتضمن كلمات عدّة.

def method_name
 puts "use underscores to separate words"
end

يجب أن تكتب البرامج بلغة روبي باستخدام مجموعات محارف متوافقة مع مجموعة المحارف US-ASCII مثل UTF-8 و ISO-8859-1. فإذا كان البتات الثمانية مضبوطة في مجموعات المحارف هذه، فهذا يدل على أنّ المحرف هو من المحارف الموسّعة، ولغة روبي تسمح أن تحتوي أسماء التوابع والمعرّفات الأخرى على تلك المحارف. إلا أنّ لغة روبي لا تسمح باستخدام بعض المحارف مثل ASCII NUL الذي رمزه ‎\x00. إليك بعض الأمثلة عن التوابع التي تصلح تسميتها في لغة روبي:

def hello
 "hello"
end
def こんにちは
 puts "means hello in Japanese"
end

وعادة ما تكون أسماء التوابع متوافقة مع نظام US-ASCII كون الحروف الخاصة به متوفرة على جميع لوحات المفاتيح. ويمكن لأسماء التوابع أن تنتهي بعلامة التعجّب ! أو علامة الاستفهام ? أو علامة المساواة =.

إن التوابع المنتهية بعلامة تعجب تستدعى وتُنفّذ مثل أيّ تابع آخر، إلّا أنّها عادة ما تصنّف كتوابع خطرة. ففي مكتبة نواة لغة روبي يُعدّ التّابع خطيرًا إذا كان يغيّر من قيمة المتغيّر الذي يستقبله، ولذلك يُميّز بعلامة التعجّب في آخره.

وفي هذه المكتبة يوجد لكلّ تابع من هذا النّوع تابعًا آخر مقابلًا ليس خطيرًا أي أنّه لا يغيّر من قيمة المتغيّر الذي يستقبله، وهذه التّوابع يكون لها نفس التسمية إلا أنّها لا تنتهي بإشارة التّعجب.

أمّا المتعارف عليه بالنّسبة للتوابع التي تنتهي تسميتها بإشارة استفهام فهي التوابع التي تُرجع قيمة منطقيّة، لكنّها لا تعيد بالضّرورة القيمة true أو false، بل غالبًا ما تعيد كائنًا يعبّر عن true أو قيمة الإيجاب.

والتوابع التي تنتهي بإشارة مساواة فهي توابع الإسناد؛ وبالنسبة لهذا النوع من التوابع، تُتجاهل القيمة المعادة وتُرجع معاملات التابع بدلًا منها.

فيما يلي أسماء التوابع المعبرة عن مختلف العمليات في لغة روبي، كلّ واحدة من هذه العمليات تقبل معاملًا واحدًا فقط. تجد بعد رمز العملية الاستخدام الشهير لها أو اسم العملية. لاحظ أنّك إن غيّرت ما تقوم به كلّ عمليّة فستسبّب لبسًا لدى المستخدم، إذ أنّ المتوقّع من إشارة الجمع أن تجمع، ومن إشارة الطرح أن تطرح، وهكذا. أضف إلى ذلك أنّك لن تتمكّن من تغيير أولويّة أيّ من هذه العمليات. 

المعامل العملية
+ جمع
- طرح
* ضرب
** قوة
/ قسمة
% باقي القسمة، أو سلسلة %
& العملية AND
^ العملية XOR
>> إزاحة لليمين
<< إزاحة لليسار، أو إضافة
== اختبار المساواة
=! اختبار عدم المساواة
=== المساواة التامة
~= مطابقة النمط (ليس خاصًّا بالتعبيرات النظامية فحسب)
~! عدم مطابقة النمط
<=> المقارنة والمعروف أيضًا بمعامل سفينة الفضاء
> أصغر من
=> أصغر من أو يساوي
< أكبر من
=< أكبر من أو يساوي

لأجل تعريف توابع أحادية تغيّر سلوك إشارة الجمع والطرح والضرب والنفي المنطقي، عليك أن تُتبع رمز العملية بالرمز @ مثل: ‎+@‎ أو ‎!@‎ كما في المثال التالي:

class C
 def -@
   puts "you inverted this object"
 end
end
obj = C.new
-obj # "you inverted this object" يطبع

التوابع الأحادية لا تقبل أي معاملات. بالإضافة لذلك يمكن تعريف التوابع التي تستخدم للإشارة إلى العناصر وإسناد قيمٍ إليها بالشّكل [] أو ‎[]=‎ على التوالي وبالترتيب. وكلاهما بالإمكان أن يأخذ معاملًا واحدًا أو أكثر. ويمكن لتابع الإشارة للعناصر ألا يأخذ أيّ معامل. 

class C
 def [](a, b)
   puts a + b
 end
 def []=(a, b, c)
   puts a * b + c
 end
end
obj = C.new
obj[2, 3]     # "5" يطبع
obj[2, 3] = 4 # "10" يطبع

القيم المعادة

تُعيد التوابع بشكل افتراضيّ آخر تعبير برمجيّ يصل إليه التنفيذ في جسم التابع؛ في المثال أعلاه، التعبير الأخير (والوحيد) الذي نُفّذ كان عمليّة جمع بسيطة 1+1. ويمكن أيضًا استخدام الكلمة المحجوزة return للتأكيد بأنّ يعيد التابع القيمة التي تليها.

def one_plus_one
 return 1 + 1
end

كما يمكن استخدام كلمة return لإنهاء تنفيذ التابع دون إكمال التعبيرات البرمجيّة التي تليها.

def two_plus_two
 return 2 + 2
 1 + 1  # لا يتمّ تنفيذ هذا السطر أبدًا
end

لاحظ أنّه - كما أشرنا سابقًا - في حالة توابع الإسناد، فإنّ القيمة المعادة تُتجاهل ويعيد التابع بدلًا منها المعامل الذي مُرّر إليه:

def a=(value)
 return 1 + value
end
p(self.a = 5) # تطبع5

أمّا القيمة المعادة فترجع فقط في حالة استدعاء التابع بشكل مباشر:

p send(:a=, 5) # تطبع 6

النطاق

الصيغة النظامية لتعريف تابع:

def my_method
 # ...
end

وبهذه الطريقة تضيف تابعًا إلى صنف (class). كما يمكنك تعريف تابع لأغراض صنف معين وذلك بإضافة كلمة class:

class C
 def my_method
   # ...
 end
end

كما يمكن تعريف تابع لكائن آخر. وتستطيع تعريف تابع للصنف نفسه (وليس للكائنات المتفرعة عنه) كالتالي:

class C
 def self.my_method
   # ...
 end
end

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

greeting = "Hello"
def greeting.broaden
 self + ", world!"
end
greeting.broaden # تعيد "Hello, world!"

كلمة self هي كلمة محجوزة في اللغة تشير إلى الغرض الحالي من وجهة نظر مفسّر اللغة، فهي تسهّل تعريف تابع الصنف في المثال السابق.

def String.hello
 "Hello, world!"
end

تعريف التابع بهذا الشكل يسمى "التابع المنفرد" (singleton method). فالتابع broaden في المثال السابق موجود في الكائن greeting فحسب، ولن تجده في أيّ متغيّر آخر من نوع السلسلة النصية string.

إعادة التعريف

عندما يصادف مفسّر لغة روبي كلمة def قبل اسم تابع معرّف مسبقًا فإنّه لا يعدّ ذلك خطأ وإنّما يعيد تعريف هذا التابع. وهذا ما يسمّى بإعادة التعريف (Overriding). إلّا أنّ هذه الإمكانيّة في لغة روبي تُعدّ خطرة ولا يستحسن استخدامها بشكل متكّرر، إلا في حالة توسيع أصناف اللغة الأساسية (core classes)، لأنّها قد تؤدي إلى نتائج غير معروفة. خذ على سبيل المثال تسلسل الأوامر التالي:

>> "43".to_i
=> 43
>> class String
>>   def to_i
>>     42
>>   end
>> end
=> nil
>> "43".to_i
=> 42

هذا سيسبب بكلّ تأكيد تخريبًا لأيّ شيفرة تستخدم التابع to_i من الصنف String لاستخلاص أعدادٍ من سلسلة نصية.

وسائط التوابع

يمكن للتابع أن يقبل وسائط وتكون مكتوبة على التتالي بعد اسم التابع:

def add_one(value)
 value + 1
end

عند استدعاء التابع add_one، يجب على المستخدم أن يمرّر له وسيطًا، وهذا الوسيط هو متغيّر محليّ ضمن جسم التابع. ففي المثال السابق، يضيف التابع واحدًا إلى هذا الوسيط الذي مُرّر إليه، ويُعيد الناتج. فإذا أُعطي التابع 1 سيعيد 2. والأقواس المحيطة بالوسائط اختيارية ويمكن حذفها:

def add_one value
 value + 1
end

وإذا كان لديك أكثر من وسيط، فافصل بينها بفواصل:

def add_values(a, b)
 a + b
end

فعند الاستدعاء، يجب تمرير الوسائط بنفس الترتيب؛ بعيارة أخرى، يمكن القول أنّ الوسائط حساسة للترتيب.

القيم الافتراضية

يمكن أن تحصل الوسائط على قيم افتراضية:

def add_values(a, b = 1)
 a + b
end

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

def add_values(a = 1, b = 2, c)
 a + b + c
end

أما هذا المثال، فسيعطي الخطأ SyntaxError:

def add_values(a = 1, b, c = 1)
 a + b + c
end

تفكيك المصفوفة

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

def my_method((a, b))
 p a: a, b: b
end
my_method([1, 2])

هذا المثال يطبع:

{:a=>1, :b=>2} 

ويُتجاهل كلّ عنصر في المصفوفة زائد على عدد الوسائط.

def my_method((a, b))
 p a: a, b: b
end
my_method([1, 2, 3])

وهذا المثال له نفس نتيجة المثال السابق. يمكنك استخدام الرمز * لتحصيل الوسائط المتبقية أيًا كان عددها في المصفوفة؛ فالمثال التالي يفصل المصفوفة ليسند أول عنصر منها في الوسيط الأول، والباقي يسنده كمصفوفة إلى الوسيط الثاني:

def my_method((a, *b))
 p a: a, b: b
end
my_method([1, 2, 3])

هذا المثال يطبع:

{:a=>1, :b=>[2, 3]}

أيّ وسيط يستجيب للتابع to_ary يمكن استخدامه في عملية التفكيك هذه. فإذا كان لديك كائن آخر تريد أن تفكّكه بنفس الطريقة، فعليك أن تعرّف له التابع to_ary الخاص به. يمكنك استخدام أقواس ضمنية لتفكيك مصفوفة مُرّرت كوسيط، لكن إذا لم يكن الوسيط المقابل له من نوع مصفوفة، فسوف يُسند هذا الوسيط إلى الوسيط الأول ضمن قوسي التفكيك، بينما تأخذ بقية الوسائط القيمة nil:

def my_method(a, (b, c), d)
 p a: a, b: b, c: c, d: d
end
my_method(1, 2, 3)

هذا المثال يطبع:

{:a=>1, :b=>2, :c=>nil, :d=>3}

كما بإمكانك تضمين عمليات التفكيك داخل بعضها بأي شكل:

def my_method(((a, b), c))
 # ...
end

الوسائط التي من النوع: مصفوفة وجدول Hash

إضافة الرمز * في بداية اسم الوسيط يسبب تحويل جميع الوسائط الإضافية إلى مصفوفة:

def gather_arguments(*arguments)
 p arguments
end
gather_arguments 1, 2, 3 # [1, 2, 3] تطبع

ويجب أن يأتي هذا الوسيط في آخر الوسائط الموضعية وعليه أن يسبق أيّ وسيط مسمى. كما يمكن للمصفوفة الناتجة أن تتضمّن جدول Hash في نهايتها إذا مُرّر هذا النوع عند الاستدعاء بعد جميع الوسائط الموضعية. 

gather_arguments 1, a: 2 # [1, {:a=>2}] تطبع

إلّا أنّ هذه الحالة تحصل فقط عندما لا يعرّف التابع أيّ وسائط من نوع "الوسائط المسماة".

def gather_arguments_keyword(*positional, keyword: nil)
p positional: positional, keyword: keyword
end
gather_arguments_keyword 1, 2, three: 3
#=> unknown keyword: three (ArgumentError):تسبب ظهور خطأ
# لاحظ أيضًا أنّ استخدام الرمز * وحده يسبب تجاهل أيّ معاملات.
def ignore_arguments(*)
end

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

الوسائط المسماة تشبه في آلية عملها الوسائط الموضعية ذات القيم الافتراضية:

def add_values(first: 1, second: 2)
 first + second
end

يمكن قبول أيّ عدد من الوسائط المسماة باستخدام الرمز ** قبل اسم الوسيط:

def gather_arguments(first: nil, **rest)
 p first, rest
end
gather_arguments first: 1, second: 2, third: 3
# {:second=>2, :third=>3} تطبع 1 ثم

عند استدعاء تابع مع وسائط مسماة، فمن الممكن لها أن تأتي ضمن أيّ ترتيب، إلّا أنّ الوسائط غير المعرفة مسبقًا ستسبب ظهور خطأ من النوع ArgumentError.

وفي حال رغبتك باستخدام وسائط مسماة مع وسائط موضعية، فعليك أن تكتب جميع الوسائط الموضعية قبل أيّ وسيط مسمى.

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

يُميّز وسيط الكتلة البرمجيّة باستخدام الرمز &، ويجب أن يكون الأخير ضمن تسلسل الوسائط:

def my_method(&my_block)
 my_block.call(self)
end

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

def each_item(&block)
 @items.each(&block)
end

إذا أردت فقط أن تنفّذ الكتلة البرمجيّة ولن تجري بالمقابل تعديلًا عليها أو إرسالها إلى تابع آخر، فيفضّل حينها أن تستخدم الكلمة المحجوزة yield دون تحديد وسيط معيّن. فالطريقة التالية تكافئ التابع الأوّل في هذه الفقرة:

def my_method
 yield self
end

وهناك فائدة أخرى على صعيد تحسين الأداء عند استخدام yield عوضًا عن الاستدعاء المباشر للكتلة؛ فعند إسناد وسيط كتلة برمجية إلى متغيّر، سيسبّب ذلك إنشاء كائن من النوع Proc والذي سيبّب إيقاف تنفيذ الكتلة مؤقتًا. أمّا عند استخدام yield، فلن يُنشأ الكائن Proc هذا بالأساس.

إذا كانت حاجتك إلى استخدام هذه الكتلة قليلة، فبإمكانك حينئذٍ أن تستخدم Proc.new لإنشاء proc من الكتلة البرمجيّة والذي يمكن تمريره إلى التابع. انظر التوثيق الخاصّ بالتابع Proc.new لمزيد من التفاصيل.

التعامل مع الاستثناءات

تحتوي التوابع على آلية تعامل مع الاستثناءات ضمنية، فلست بحاجة لاستخدام begin - end للتعامل مع الاستثناء. فهذا المثال: 

def my_method
 begin
   # شيفرة قد تسبب ظهور استثناءات
 rescue
   # التعامل مع الاستثناء
 end
end

يمكن كتابته بالشكل التالي:

def my_method
 # شيفرة قد تسبب ظهور استثناء
rescue
 # التعامل مع الاستثناء
end

إذا أردت تفادي استثناء في جزء محدد من التابع فقط، فاستخدم حينها begin - end. لمزيد من التفاصيل ارجع إلى الصفحة الخاصة بالتعامل مع الاستثناءات.

المصادر