الدليل السريع إلى لغة البرمجة روبي Ruby


إبراهيم البحيصي

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

  1. كتابة التعليقات.
  2. المصفوفات.
  3. جمل التحكم.
  4. معالجة الخطأ.
  5. بناء الوظائف.
  6. جملة yield.
  7. الفئات.

ملاحظة: ناتج تنفيذ الأمثلة والأوامر الموجودة في هذا المقال تقع بعد علامة #=> من كل نتيجة أو أمر.

كتابة التعليقات

# هذا تعليق

=begin
تعليق من أكثر من سطر
=end

قبل البدء، لا بد من التنويه أن كل شيء عبارة عن كائن في لغة روبي. الأرقام عبارة عن كائنات، والوظيفة class في السطر التالي تعيد نوع الكائن:

3.class #=> Fixnum

3.to_s #=> "3"

العمليات الحسابية والثنائية

1 + 1 #=> 2
8 - 1 #=> 7
10 * 2 #=> 20
35 / 5 #=> 7
2**5 #=> 32
5 % 3 #=> 2
3 & 5 #=> 1
3 | 5 #=> 7
3 ^ 5 #=> 6

العمليات الرياضية سهلة الاستدعاء على مستوى الكائن:

1.+(3) #=> 4
10.* 5 #=> 50

بعض القيم تُعتبر كائنات مثل:

nil # تشبه القيمة الفارغة في اللغات الأخرى
true # صحيح منطقي
false # خطأ منطقي
nil.class #=> NilClass
true.class #=> TrueClass
false.class #=> FalseClass

المساواة:

1 == 1 #=> true
2 == 1 #=> false

اللامساواة:

1 != 1 #=> false
2 != 1 #=> true

تُعتبر القيمة الفارغة مرادفة للقيمة المنطقية الخاطئة:

!nil   #=> true
!false #=> true
!0     #=> false

المقارنات:

1 < 10 #=> true
1 > 10 #=> false
2 <= 2 #=> true
2 >= 2 #=> true

عمليات المقارنة المُجمعة:

1 <=> 10 #=> -1
10 <=> 1 #=> 1
1 <=> 1 #=> 0

العمليات المنطقية:

true && false #=> false
true || false #=> true
!true #=> false

يوجد نسخة أخرى من العمليات المنطقية ولكن بتطبيق مفهوم الأولوية المنخفضة، مما يعني استخدامها كبناء للتحكم في التدفقات (Flow Control) وربط الجمل ببعضها حتى تقوم أحدها بإرجاع قيمة منطقية صحيحة أو خاطئة.
فمثلا، في السطر التالي تُستدعى do_something_else في حال كان استدعاء do_something ناجحاً.

do_something() and do_something_else()

وهنا يُستدعى log_error في حال فشل استدعاء: do_someting

do_something() or log_error()

النصوص

النصوص عبارة عن كائنات:

'I am a string'.class #=> String
"I am a string too".class #=> String

placeholder = 'use string interpolation'
"I can #{placeholder} when using double quoted strings"
#=> "I can use string interpolation when using double quoted strings"

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

'hello ' + 'world'  #=> "hello world"
'hello ' + 3 #=> TypeError: can't convert Fixnum into String
'hello ' + 3.to_s #=> "hello 3"

دمج النصوص مع العمليات:

'hello ' * 3 #=> "hello hello hello "

الإضافة لنص:

'hello' << ' world' #=> "hello world"

لطباعة نص وسطر في النهاية نستخدم وظيفة puts:

puts "I'm printing!"
#=> I'm printing!
#=> nil

طباعة نص دون سطر في النهاية:

print "I'm printing!"
#=> I'm printing! => nil

المتغيرات

تعريف المتغيرات:

x = 25 #=> 25
x #=> 25

استخدام عملية المساواة تُرجع القيمة المستخدمة وهذا يعني أنك تستطيع إجراء عمليات مساواة متعددة كما المثال التالي:

x = y = 10 #=> 10
x #=> 10
y #=> 10

من المتعارف عليه استخدام طريقة snake_case في تسمية المتغيرات:

snake_case = true

حاول أن تستخدم أسماء متغيرات ذات دلالة:

path_to_project_root = '/good/name/'
path = '/bad/name/'

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

:pending.class #=> Symbol
status = :pending
status == :pending #=> true
status == 'pending' #=> false
status == :approved #=> false

المصفوفات

لتعريف مصفوفة نقوم بالتالي:

array = [1, 2, 3, 4, 5] #=> [1, 2, 3, 4, 5]

من الممكن أن تحتوي المصفوفة على عناصر ذات أنواع مختلفة:

[1, 'hello', false] #=> [1, "hello", false]

يتم فهرسة المصفوفات بطريقة أمامية:

array[0] #=> 1
array.first #=> 1
array[12] #=> nil

ومن الممكن فهرستها بطريقة عكسية:

array[-1] #=> 5
array.last #=> 5

ومن الممكن تحديد فهرس البداية والنهاية للحصول على جزء أو شريحة من المصفوفة:

array[2, 3] #=> [3, 4, 5]

نستخدم وظيفة reverse لإجراء عملية عكس المصفوفة:

a=[1,2,3]
a.reverse! #=> [3,2,1]

من الممكن أيضا أن نستخدم نطاقًا لإجراء عملية الاقتطاع من المصفوفة بالطريقة التالية:

array[1..3] #=> [2, 3, 4]

لإجراء عملية الإضافة على المصفوفة نقوم بالتالي:

array << 6 #=> [1, 2, 3, 4, 5, 6]

أو :

array.push(6) #=> [1, 2, 3, 4, 5, 6]

للتأكد من وجود قيمة في المصفوفة نستخدم الوظيفة include :

array.include?(1) #=> true

هاش Hash

الهاش Hash في لغة روبي هو القاموس الرئيسي باستخدام المفتاح والقيمة، ولتعريف الهاش نستخدم الأقواس المزخرفة:

hash = { 'color' => 'green', 'number' => 5 }
hash.keys #=> ['color', 'number']

يتم البحث في الهاش باستخدام المفتاح بالطريقة التالية:

hash['color'] #=> 'green'
hash['number'] #=> 5

في حالة البحث في الهاش باستخدام مفتاح غير موجود فإن النتيجة المرجعة هي nil :

hash['nothing here'] #=> nil

بعد نسخة روبي 1.9 يوجد طريقة خاصة لاستخدام الرموز كمفاتيح للهاش:

new_hash = { defcon: 3, action: true }
new_hash.keys #=> [:defcon, :action]

لفحص وجود مفتاح أو قيمة في الهاش نستخدم الطريقة التالية:

new_hash.key?(:defcon) #=> true
new_hash.value?(3) #=> true

ملاحظة/ المصفوفات والهاش في الروبي قابلة للعد (Enumerable) ، وكلاهما يحتوي على مجموعة من الوظائف المفيدة.

جمل التحكم

جملة الشرط:

if true
  'if statement'
elsif false
  'else if, optional'
else
  'else, also optional'
end

جملة التكرار for:

for counter in 1..5
  puts "iteration #{counter}"
end
#=> iteration 1
#=> iteration 2
#=> iteration 3
#=> iteration 4
#=> iteration 5

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

(1..5).each do |counter|
  puts "iteration #{counter}"
end
#=> iteration 1
#=> iteration 2
#=> iteration 3
#=> iteration 4
#=> iteration 5

نستطيع إحاطة كتلة الشفرة البرمجية بأقواس مزخرفة:

 (1..5).each { |counter| puts "iteration #{counter}" }

نستطيع استخدام each للمرور على محتويات التراكيب مثل المصفوفات والهاش:

array.each do |element|
  puts "#{element} is part of the array"
end
hash.each do |key, value|
  puts "#{key} is #{value}"
end

إذا كنت تريد الحصول على فهرس العنصر الذي تمر عليه في جملة each تستطيع استخدام جملة each_with_index وتعريف متغير الفهرس من خلالها.
انظر المثال التالي:

array.each_with_index do |element, index|
  puts "#{element} is number #{index} in the array"
end
counter = 1
while counter <= 5 do
  puts "iteration #{counter}"
  counter += 1
end
#=> iteration 1
#=> iteration 2
#=> iteration 3
#=> iteration 4
#=> iteration 5

توجد مجموعة من الوظائف الأخرى لتنفيذ الحلقات Loops في لغة الروبي، فمثلا توجد map، reduce ، inject والقائمة تطول.
Map تأخذ مصفوفة كمعامل، وتقوم بالمرور على عناصرها وإجراء عمليات عليها وترجعها في مصفوفة جديدة، كما المثال التالي:

array = [1,2,3,4,5]
doubled = array.map do |element|
  element * 2
end
puts doubled
#=> [2,4,6,8,10]
puts array
#=> [1,2,3,4,5]

جملة case :

grade = 'B'
case grade
when 'A'
  puts 'Way to go kiddo'
when 'B'
  puts 'Better luck next time'
when 'C'
  puts 'You can do better'
when 'D'
  puts 'Scraping through'
when 'F'
  puts 'You failed!'
else
  puts 'Alternative grading system, eh?'
end
#=> "Better luck next time"

نستطيع استخدام نطاق مع جملة case بالطريقة التالية:

grade = 82
case grade
when 90..100
  puts 'Hooray!'
when 80...90
  puts 'OK job'
else
  puts 'You failed!'
end
#=> "OK job"

معالجة الخطأ

begin
  raise NoMemoryError, 'You ran out of memory.'
rescue NoMemoryError => exception_variable
  puts 'NoMemoryError was raised', exception_variable
rescue RuntimeError => other_exception_variable
  puts 'RuntimeError was raised now'
else
  puts 'This runs if no exceptions were thrown at all'
ensure
  puts 'This code always runs no matter what'
end

بناء الوظائف والدوال

def double(x)
  x * 2
end

الوظائف ضمنيا تعيد قيمة آخر جملة في الوظيفة:

double(2) #=> 4

الأقواس تُعتبر إضافية، ومن الممكن استدعاء الوظيفة من دونهم:

double 3 #=> 6

double double 3 #=> 12

def sum(x, y)
  x + y
end

معاملات الوظائف يتم الفصل بينها بواسطة الفاصلة.

sum 3, 4 #=> 7
sum sum(3, 4), 5 #=> 12

جملة yield

كل الوظائف تمتلك ضمنيا معامل كتلة إضافي خاص بها، وتُستدعى بواسطة كلمة yield :

def surround
  puts '{'
  yield
  puts '}'
end
surround { puts 'hello world' }
# {
# hello world
# }

تستطيع تمرير كتلة من الشفرة البرمجية للوظيفة، ونستخدم رمز & لحفظ عنوان كتلة الشفرة البرمجية المُمَرَرَة.

def guests(&block)
  block.call 'some_argument'
end

تستطيع تمرير أكثر من معامل للوظيفة بشكل غير محدد باستخدام رمز *، وهذه المجموعة من المعاملات تتحول إلى مصفوفة والتي بدورك تستطيع المرور عليها باستخدام جملة each:

def guests(*array)
  array.each { |guest| puts guest }
end

إذا كانت الوظيفة تُرجع مصفوفة، فإنك تستطيع استخدام المساواة المتعددة لأكثر من متغير في نفس الوقت (unpacking):

def foods
    ['pancake', 'sandwich', 'quesadilla']
end
breakfast, lunch, dinner = foods
breakfast #=> 'pancake'
dinner #=> 'quesadilla'

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

5.even? # false
5.odd? # true

إذا كانت الوظيفة تنتهي بعلامة تعجب، فهذا يعني أن التغيير الذي يتم على المتغير أو العنصر يكون مباشرا على قيمته، أما بدون علامة تعجب، فإن العملية لا تؤثر على العنصر، ويتم إعادة التغيير في عنصر جديد. انظر للمثال التالي:

company_name = "Dunder Mifflin"
company_name.upcase #=> "DUNDER MIFFLIN"
company_name #=> "Dunder Mifflin"
company_name.upcase! # we're mutating company_name this time!
company_name #=> "DUNDER MIFFLIN"

الأصناف Classes

تُعرَّف الأصناف باستخدام الكلمة المحجوزة class:

class Human

نعرّف في ما يلي متغيرًا على مستوى الصنف، وهو مُشارَك بين الكائنات المتولدة الخاصة بهذا الصنف:

  @@species = 'H. sapiens'

الطريقة الأساسية للاستهلال Initialization :

  def initialize(name, age = 0)

إعطاء قيمة المعامل “الاسم” للمتغير الخاص بالكائن المتولد من الفئة بنفس الاسم

    @name = name

في حالة عدم تمرير معامل باسم “العمر” فإن القيمة التلقائية هي التي ستمرر للمتغير الخاص بالكائن المتولد:

    @age = age
  end

وظيفة التعديل الأساسية (Setter):

  def name=(name)
    @name = name
  end

وظيفة الاسترجاع الأساسية (Getter):

  def name
    @name
  end

نستطيع تنفيذ وظيفتي التعديل والاسترجاع بواسطة attr_accessor كالتالي:

  attr_accessor :name

ويمكن فصل العمليتين عن بعضهما.
المُسترجِع :getter

  attr_reader :name

المعدِّل setter:

  attr_writer :name

للتمييز بين الوظائف الخاصة بالصنف، والوظائف الخاصة بالكائن المتولد من الصنف، نستخدم كلمة self، وهي خاصة لتعريف الوظائف على مستوى الفئة.

  def self.say(msg)
    puts msg
  end

  def species
    @@species
  end
end

تعريف كائنين من الصنف Human:

jim = Human.new('Jim Halpert')
dwight = Human.new('Dwight K. Schrute')

استدعاء بعض الوظائف:

jim.species #=> "H. sapiens"
jim.name #=> "Jim Halpert"
jim.name = "Jim Halpert II" #=> "Jim Halpert II"
jim.name #=> "Jim Halpert II"
dwight.species #=> "H. sapiens"
dwight.name #=> "Dwight K. Schrute"

استدعاء الوظيفة على مستوى الصنف:

Human.say('Hi') #=> "Hi"

يُعرَّف مجال المتغيرات في المكان التي عُرِّف فيه المتغيّر، والمتغيرات التي تبدأ ب علامة $ تكون على مستوى النطاق الواسع Global Variable:

$var = "I'm a global var"
defined? $var #=> "global-variable"

المتغيرات التي تبدأ بعلامة @ تكون على مستوى الكائن المتولد:

@var = "I'm an instance var"
defined? @var #=> "instance-variable"

المتغيرات التي تبدأ ب @@ تكون على مستوى الصنف:

@@var = "I'm a class var"
defined? @@var #=> "class variable"

المتغيرات التي تبدأ بحرف كبير تكون ثوابتا:

Var = "I'm a constant"
defined? Var #=> "constant"

المتغير الخاص بالصنف يتشاركه الصنف وكل الأصناف التي تريث منه:

# base class
class Human
  @@foo = 0

  def self.foo
    @@foo
  end

  def self.foo=(value)
    @@foo = value
  end
end

# derived class
class Worker < Human
end

Human.foo # 0
Worker.foo # 0

Human.foo = 2 # 2
Worker.foo # 2

المتغير الخاص بالكائن المتولد من الصنف غير مشارك أو مرئي في الأصناف التي ترث من الصنف الرئيسي:

class Human
  @bar = 0

  def self.bar
    @bar
  end

  def self.bar=(value)
    @bar = value
  end
end

class Doctor < Human
end

Human.bar # 0
Doctor.bar # nil

عند استخدام عملية include لتضمين وحدة Module داخل صنف، فإن الوظائف الخاصة بالوحدة تكون مضمنة في الكائنات المتولدة من الصنف.
وعند استخدام عملية extend للوحدة داخل صنف، فإن الوظائف الخاصة بالوحدة تكون مضمنة في نفس الصنف.

module ModuleExample
  def foo
    'foo'
  end
end

class Person
  include ModuleExample
end

class Book
  extend ModuleExample
end

Person.foo     # => NoMethodError: undefined method `foo' for Person:Class
Person.new.foo # => 'foo'
Book.foo       # => 'foo'
Book.new.foo   # => NoMethodError: undefined method `foo'

تُنفَّذ دوال استرداد Callbacks عندما تُطبَق include أو extend على الوحدة :

module ConcernExample
  def self.included(base)
    base.extend(ClassMethods)
    base.send(:include, InstanceMethods)
  end

  module ClassMethods
    def bar
      'bar'
    end
  end

  module InstanceMethods
    def qux
      'qux'
    end
  end
end

class Something
  include ConcernExample
end

Something.bar     # => 'bar'
Something.qux     # => NoMethodError: undefined method `qux'
Something.new.bar # => NoMethodError: undefined method `bar'
Something.new.qux # => 'qux'

ترجمة – بتصرّف – للمقال Learn X in Y minutes Where X=ruby.





تفاعل الأعضاء


لا توجد أيّة تعليقات بعد



يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن