الإسناد في لغة روبي
لكي نسند شيء في لغة روبي، نستخدم رمز المساواة =
؛ ففي المثال التّالي، يُسنَد العدد 5 إلى المتغيّر v
المحلّي:
v = 5
فالإسناد يُنشئ متغيّرًا محلّيًا جديدًا إذا لم يكن قد عُرِّفَ من قبل.
أسماء المتغيرات المحلية
اسم المتغيّر المحلّي يجب أن يبدأ بحرف صغير من مجموعة المحارف US-ASCII أو من مجموعة المحارف التي تُمثَّل باستخدام ثمان بتات ثنائيّة. وبشكلٍ عام، فإنّ أسماء المتغيّرات المحلّية متوافقة مع US-ASCII كون الأزرار التي تستخدم لكتابتها موجودة في جميع لوحات المفاتيح.
عمومًا، جميع البرامج المكتوبة بلغة روبي يجب أن تستخدم محارفًا متوافقة مع مجموعة المحارف US-ASCII. في مجموعات كهذه من المحارف، إذا كان البت الثامن بقيمة 1 فهذا يدلّ على أنّ المحرف ينتمي للمحارف الموسّعة (extended character)، ولغة روبي تسمح أن تحتوي أسماء المتغيّرات هذا النوع من المحارف.
كما يُمكن لاسم المتحوّل أن يحتوي على حروفٍ وأرقامٍ ورمز الشرطة السفليّة (underscore) "_".
نطاق المتغيرات المحلية
بمجرّد أن يتمّ إسناد قيمة إلى المتغيّر المحلّيّ فكلّ استخدامات اسم هذا المتغيّر ضمن النطاق نفسه سيتمّ اعتبارها متغيّرات محليّة. وإليك هذا المثال:
1.times do
a = 1
puts "local variables in the block: #{local_variables.join ", "}"
end
puts "no local variables outside the block" if local_variables.empty?
وتكون النتيجة طباعة التالي:
local variables in the block: a
no local variables outside the block
بما أنّ الكتلة البرمجيّة block
تنشئ نطاقًا جديد فلن يكون من الممكن استخدام أي متغيّر محليّ يُنشَأ ضمن هذا النطاق في النطاقات الأخرى خارجه. لكن يُمكن للمتغيّرات المعرّفة في نطاق ما أن تُستخدم في النّطاقات الأخرى المتضمّنة فيه كالتّالي:
a = 0
1.times do
puts "local variables: #{local_variables.join ", "}"
end
وتكون النتيجة طباعة التّالي:
local variables: a
ومن جهة أخرى، قد تودّ أن تعزل المتغيّرات المحليّة المعرّفة ضمن كتلة برمجيّة عن النّطاق الخارجيّ الذي يتضمنّها، في هذه الحالة عليك كتابة هذه المتغيّرات ضمن الكتلة البرمجيّة وكتابة فاصلة منقوطة بعد هذه المتغيّرات. لمثال على ذلك ارجع إلى التوثيق الخاص باستدعاء التوابع، واقرأ فقرة المتغيّرات المحليّة ضمن الكتل البرمجيّة.
انظر أيضًا توثيق التابع Kernel.local_variables
، لكن انتبه إلى أنّ الحلقات من نوع for
لا تنشئ نطاقًا خاصًّا بها كما هو الحال في الكتل البرمجيّة.
التوابع والمتغيرات المحلية
أسماء المتغيّرات المحليّة والتوابع في لغة روبي متماثلة تقريبًا. فإذا عرّفت اسمًا دون أن تسندَ إليه أيّ قيمة فسيُعدّ في لغة روبي على أنّه تابع. وبمجرّد أن تُسند إليه قيمة فسيُعدّ متغيّرًا.
يُنشَأ المتغيّر المحليّ عندما يمرّ مُفسِّر روبي على عمليّة الإسناد، وليس عندما تقع هذه العمليّة بالفعل، كما في المثال التّالي:
a = 0 if false # لا يحصل الإسناد هنا
p local_variables # يطبع [:a]
p a # يطبع nil
هذا التشابه بين أسماء المتغيّرات والتوابع قد يؤدّي في بعض الأحيان إلى صعوبة في فهم الشيفرة، فعلى سبيل المثال:
def big_calculation
42 # تتظاهر بأنّها عمليّة تأخذ الكثير من الوقت
end
big_calculation = big_calculation()
أيّ إشارة هنا إلى big_calculation
ستُعدّ على أنّها متغيّر محليّ وسيُحتفَظ بها في الذاكرة المخبئة (cache). وإذا أردت بدلًا من ذلك استدعاء التابع ذي الاسم نفسه فعليك أن تكتب self.big_calculatoin
.
يمكنك أيضًا أن تُؤكّد على استدعاء التابع وذلك باستخدام أقواس فارغة كما يظهر في المثال، أو باستخدام مستقبِل محدّد مثل self
. لكنّ الطّريقة الثّانية قد تسبب ظهور الخطأ NameError
إذا كان التابع غير متاح للاستدعاء العام (أي أنه ليس تابعًا عامًّا).
وهناك حالة أخرى شائعة قد تسبّب التباسًا وتحصل عند استخدام if
كالتّالي:
p a if a = 0.zero?
فبدلًا من طباعة "true" سيظهر الخطأ "NameError, undefined local variable or method 'a'. والسّبب في ذلك يعود إلى أنّ روبي ترى المتغير a
الذي قبل if
أوّلًا قبل المتغير a
الآخر وتحسب أنّك تريد استدعاء تابع، ثمّ إذا مرّت على المتغير a
الآخر فستفترض أنّك تريد الإشارة إلى تابع محليّ.
هذا اللبس يحصل لأنّ تنفيذ العبارة البرمجيّة يحصل بعكس الترتيب المنشود، فالإسناد يتمّ أوّلًا ثمّ يحصل استدعاء لتابع غير موجود.
متغيرات النسخة (Instance Variables)
هي متغيّرات تُشارَك عبر كلّ التوابعّ التابعة لنفس الكائن. هذه المتغيّرات يجب أن تبدأ بالرّمز @
، وعدا ذلك فهي تتبع نفس القواعد في تسميّة المتغيّرات المحليّة. وطالما أنّها تبدأ بالرّمز @
فيُمكن للحرف الثّاني أن يكون حرفًا كبيرًا (upper case).
وهذا مثال على استخدام متغيّرات النسخة:
class C
def initialize(value)
@instance_variable = value
end
def value
@instance_variable
end
end
object1 = C.new "some value"
object2 = C.new "other value"
p object1.value # تطبع "some value"
p object2.value # تطبع "other value"
متغيّرات النسخة التي لم يتمّ تهيئتها بقيمة ابتدائيّة تأخذ القيمة nil
تلقائيًّا. وإذا شغّلتَ روبي بوضع تفعيل التحذيرات (warnings) فسيظهر لك تنبيهًا عند كلّ محاولة لاستخدام متغيّر حالة لم تتم تهيئته.
يمكن للتابع value
أن يصل إلى القيمة التي ضُبِطَت في التابع initialize
، وذلك فقط للكائن نفسه.
متغيرات الصنف (Class Variables)
وهي متغيّرات مشتركة بين الصنف والأصناف المتفرّعة منه والكائنات المتولّدة عنه.
يجب أن يبدأ متغيّر الصنف بالرمزين @@
، وتتمة الاسم تتبع لنفس أسلوب تسمية متغيّرات الحالة.
إليك هذا المثال:
class A
@@class_variable = 0
def value
@@class_variable
end
def update
@@class_variable = @@class_variable + 1
end
end
class B < A
def update
@@class_variable = @@class_variable + 2
end
end
a = A.new
b = B.new
puts "A value: #{a.value}"
puts "B value: #{b.value}"
وعند تنفيذه يطبع التّالي:
A value: 0
B value: 0
وبالاستمرار مع نفس المثال، يمكننا تحديث قيمة المتغيّر باستخدام كائنٍ من أيّ من الصنفين، وستُشارك القيمة الجديدة بينهما:
puts "update A"
a.update
puts "A value: #{a.value}"
puts "B value: #{b.value}"
puts "update B"
b.update
puts "A value: #{a.value}"
puts "B value: #{b.value}"
puts "update A"
a.update
puts "A value: #{a.value}"
puts "B value: #{b.value}"
وتكون النتيجة:
update A
A value: 1
B value: 1
update B
A value: 3
B value: 3
update A
A value: 4
B value: 4
عند محاولة الوصول إلى متغيّر صنف لم تتمّ تهيئته فسيظهر استثناء من نوع NameError
.
لاحظ أنّ الأصناف لديها أيضًا متغيّرات النسخة وذلك لأنّ الأصناف هي كائنات أيضًا، لذلك حاول ألّا تخلط بين متغيّرات الأصناف ومتغيّرات النسخة.
المتغيرات العامة (Global Variables)
المتغيّرات العامّة يمكن الوصول إليها في كلّ مكان وهي تبدأ بالرّمز $
وباقي الاسم يتبع نفس أسلوب تسمية متغيّرات النسخة.
إليك المثال التالي:
$global = 0
class C
puts "in a class: #{$global}"
def my_method
puts "in a method: #{$global}"
$global = $global + 1
$other_global = 3
end
end
C.new.my_method
puts "at top-level, $global: #{$global}, $other_global: #{$other_global}"
والنتيجة التي يطبعها هي:
in a class: 0
in a method: 0
at top-level, $global: 1, $other_global: 3
المتغيّرات العامّة التي لا يتم تهيئتها تكون قيمتها nil
.
هناك بعض المتغيّرات العامّة المعرّفة مسبقًا في لغة روبي والتي قد تؤدّي أدوارًا مختلفة حسب السّياق كما هو الحال بالنّسبة لمتغيّرات المطابقة في التعابير النمطية (regular expressions)، وأحيانًا يكون لهذه المتغيّرات العامّة تأثيرات جانبيّة عندما يتمّ الإسناد إليها. لمزيد من التّفاصيل راجع توثيق المتغيّرات العامّة.
توابع للإسناد؟
يمكنك تعريف توابع تؤدّي دور عمليّة الإسناد كالتّالي:
class C
def value=(value)
@value = value
end
end
c = C.new
c.value = 42
استخدام هذه التوابع تجعل برنامجك يظهر بشكل أجمل. ومعظم المبرمجين يستخدمون Module.attr_accessor
عند الإسناد إلى متغير نسخة:
class C
attr_accessor :value
end
عند استخدام الإسناد باستخدام التوابع، فمن اللازم وجود مستقبِل؛ وفي حال عدم وجوده، فستفترض لغة روبي أنّك تُسند إلى متغيّر محليّ.
class C
attr_accessor :value
def my_method
value = 42
puts "local_variables: #{local_variables.join ", "}"
puts "@value: #{@value.inspect}"
end
end
C.new.my_method
وتكون النتيجة طباعة التّالي:
local_variables: value
@value: nil
لذلك، فعند استخدام دوال الإسناد، عليك تحديد مستقبِل:
class C
attr_accessor :value
def my_method
self.value = 42
puts "local_variables: #{local_variables.join ", "}"
puts "@value: #{@value.inspect}"
end
end
C.new.my_method
وتكون النتيجة:
local_variables:
@value: 42
الإسناد المختصر (Abbreviated Assignment)
يمكنك الجمع بين أكثر من معامل مع عملية الإسناد، فعلى سبيل المثال، يمكنك عمل التّالي:
a = 1
a += 2
p a # تطبع 3
وهذا يكافئ:
a = 1
a = a + 2
p a # تطبع 3
وعلى نفس المنوال، يمكنك استخدام هذه المعاملات أيضًا: +
، -
، *
، /
، %
، **
، &
، |
، ^
، <<
، >>
.
ومن الممكن أيضًا استخدام =||
وفي هذه الحالة يتمّ الإسناد فقط إذا كانت القيمة nil
أو false
، وبالمثل يمكن استخدام =&&
فيتمّ الإسناد فقط إذا لم تكن القيمة nil
أو false
.
اطلع على المثال التالي الذي يشرح ذلك:
a ||= 0
a &&= 1
p a # تطبع 1
لاحظ أنّ هذين المعاملين يكافئان a || a = 0
أكثر من a = a || 0
.
إسناد مصفوفات ضمنيًّا
يمكنك إنشاء مصفوفة وإسنادها ضمن عمليّة الإسناد وذلك بكتابة العناصر متتالية يفصل بينها فواصل كالتّالي:
a = 1, 2, 3
p a #[1, 2, 3] تطبع
وهكذا يتمّ إنشاء مصفوفة ضمنيًّا.
يمكنك استخدام المعامل *
أو معامل "الفصل" لتفكيك مصفوفة أثناء عملية الإسناد. وهذا يشابه الإسناد المتعدد. ألقٍ نظرة على المثال التالي:
a = *[1, 2, 3]
p a #[1, 2, 3] تطبع
كما يمكنك الفصل في أيّ موضع على الجانب اليمينيّ من الإسناد:
a = 1, *[2, 3]
p a #[1, 2, 3] تطبع
الإسناد المتعدّد
يمكنك في لغة روبي أن تسند عدّة قيم على الجانب الأيمن إلى عدّة متغيّرات على الجانب الأيسر كالتّالي:
a, b = 1, 2
p a: a, b: b #{:a=>1, :b=>2} تطبع
وبنفس الطريقة يمكنك استخدام الإسناد المتعدّد مع أيّ نوع من المتغيّرات، سواء كانت متغيّرات أصناف أو متغيّرات نسخة أو متغيرات عامّة، وحتّى الإسناد باستخدام التوابع كما في المثال:
def value=(value)
p assigned: value
end
self.value, $global = 1, 2 #{:assigned=>1} تطبع
p $global # تطبع 2
كما يمكنك استخدام الإسناد المتعدّد للتّبديل بين قيمتي متغيّرين بشكل مباشر:
old_value = 1
new_value, old_value = old_value, 2
p new_value: new_value, old_value: old_value
#{:new_value=>1, :old_value=>2} تطبع
في حال وجود عدد من القيم على الجانب الأيمن أكثر من عدد المتغيّرات التي ستُسند إليها على الجانب الأيسر، فعندها يتمّ تجاهل القيم الزائدة الموجودة على الجانب الأيمن:
a, b = 1, 2, 3
p a: a, b: b #{:a=>1, :b=>2} تطبع
يمكنك استخدام المعامل *
في الإسناد المتعدّد لتجميع عددٍ من القيم وإسنادها إلى متغير واحد:
a, *b = 1, 2, 3
p a: a, b: b #{:a=>1, :b=>[2, 3]} تطبع
ويمكنك وضع هذا المعامل في أيّ مكان على الجانب الأيسر:
*a, b = 1, 2, 3
p a: a, b: b #{:a=>[1, 2], :b=>3} تطبع
لكن لا يمكنك استخدامه إلّا مرّة واحدة في كلّ عمليّة إسناد.
تفكيك المصفوفات
كما أنّه بإمكانك تفكيك المصفوفات عند تمرير القيم إلى التوابع، فكذلك بالإمكان تفكيك المصفوفة عند الإسناد وذلك باستخدام الأقواس المنحنية ()
كالتّالي:
(a, b) = [1, 2]
p a: a, b: b #{:a=>1, :b=>2} تطبع
يمكنك أيضًا تفكيك المصفوفة ضمن عمليّة الإسناد المتعدّد:
a, (b, c) = 1, [2, 3]
p a: a, b: b, c: c #{:a=>1, :b=>2, :c=>3} تطبع
وبما أنّ عمليّة التفكيك تعتبر عمليّة إسناد متعدّد في حد ذاتها، فبالتالي يمكنك استخدام المعامل *
ضمنها كالتّالي:
a, (b, *c), *d = 1, [2, 3, 4], 5, 6
p a: a, b: b, c: c, d: d
#{:a=>1, :b=>2, :c=>[3, 4], :d=>[5, 6]} تطبع