Ruby/assignment
الإسناد في لغة روبي
للإسناد في لغة روبي نستخدم رمز المساواة =، ففي المثال التّالي يُسنَد الرّقم 5 إلى المتغيّر المحلّي v:
v = 5
فالإسناد يُنشئ متغيّرًا محلّيًا جديدًا إذا لم يكن قد عُرِّفَ من قبل.
أسماء المتغيّرات المحليّة
اسم المتغيّر المحلّي يجب أن يبدأ بحرف صغير من مجموعة المحارف US-ASCII أو من مجموعة المحارف التي تُمثَّل باستخدام ثمان خانات ثنائيّة 8-bits. وبشكلٍ عام فإنّ أسماء المتغيّرات المحلّية متوافقة مع 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
ومن جهة أخرى، قد تودّ أن تعزل المتغيّرات المحليّة المعرّفة ضمن كتلة برمجيّة عن النّطاق الخارجيّ الذي يتضمنّها، في هذه الحالة عليك كتابة هذه المتغيّرات ضمن الكتلة البرمجيّة وكتابة فاصلة منقوطة بعد هذه المتغيّرات. لمثال على ذلك ارجع إلى التوثيق الخاص باستدعاء التوابع، واقرأ فقرة المتغيّرات المحليّة ضمن الكتل البرمجيّ.
انظر أيضًا فقرة النواة-المتغيّرات المحليّة، لكن انتبه إلى أنّ الحلقات من نوع for لا تنشئ نطاقًا خاصًّا بها كما هو الحال في الكتل البرمجيّة.
التوابع والمتغيّرات المحليّة
أسماء المتغيّرات المحليّة والتوابع في لغة روبي متماثلة تقريبًا. فإذا عرّفت اسمًا دون أن تسندَ إليه أيّ قيمة فسيُعدّ في اللّغة على أنّه تابع. وبمجرّد أن تُسند إليه قيمة فسيُعدّ متغيّرًا.
يُنشَأ المتغيّر المحليّ عندما يمرّ مُفسِّر اللّغة parser على عمليّة الإسناد، وليس عندما تقع هذه العمليّة بالفعل، كما في المثال التّالي:
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
الإسناد المختصر
يمكنك الجمع بين أكثر من معامل مع عملية الإسناد، فعلى سبيل المثال، يمكنك عمل التّالي:
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]}
مصادر