דייט ראשון עם רובי

16/02/2017

רובי (Ruby) היא השפה הכי מדליקה שאתם לא כותבים בה. היא תופסת מקום טוב בנישה בין perl ל Python ומציעה חווית פיתוח טובה בפרדיגמת פיתוח מונחה עצמים ואוסף עצום של ספריות הרחבה לכל משימה שאפשר לדמיין.

1. תוכנית Hello World

אני אוהב לראות שפה דרך דוגמאות אז נתחיל בתחביר ונתקדם צעד אחרי צעד משם. תוכנית שלום עולם ברובי נראית בערך כך:

# Hello World program in ruby
# Save in a file named hello.rb and run
# using ruby hello.rb
#
# The program asks a user for her name
# and then types a greetings

print "who are you? "
name = gets.chomp
puts "Hello #{name}! Nice to meet you"

הפקודה print מדפיסה שורה בלי תו ירידת שורה בסוף, הפקודה gets מקבלת שורת קלט מהמשתמש והפקודה puts מדפיסה שורה כולל תו ירידת שורה בסופה. ושווה גם לציין שאין צורך בנקודה פסיק בסוף שורה.

ברובי כל דבר הוא אוביקט וכל הפונקציות שמפעילים על דברים הן קריאה למתודות של אוביקטים. זו הסיבה ש chomp הופעלה בתור מתודה של המחרוזת ש gets החזירה. ערך ההחזר שלה, שהוא המחרוזת בלי תו ירידת השורה, נשמר במשתנה name.

בדומה ל Python הגדרת המשתנים היא מובלעת וקורית בעת השמת ערך ראשוני למשתנה.

2. לולאות

שתי תכונות שאני אוהב ברובי מופיעות כבר בדקות הראשונות עם השפה כשעוברים לכתוב לולאות: האחת היא שכל דבר הוא אוביקט והשניה היכולת להעביר בלוקים למתודות. נראה איך הדברים משתלבים בתוכנית:

10.times do |i|
  print "#{i}, "
end
print "\n"

לולאת ה for המסורתית הפכה למתודה times של מספר שלם, ולכן קוד התוכנית הוא קריאה למתודה זו. בנוסף, המילה do מתחילה משהו שנקרא בלוק- זהו מבנה הדומה לפונקציה אנונימית המשמש להעברת קוד לפונקציה אחרת. בלוק יכול לקבל פרמטרים, כמו בדוגמא שלנו שקיבל את האינדקס. המתודה times מקבלת את הבלוק כפרמטר ומפעילה אותו n פעמים, בכל פעם מעבירה את אינדקס הלולאה.

מבנה זה מלווה אותנו גם בלולאות נוספות ברובי, כך סריקה של מערך תיראה כך:

['I', 'See', 'Dead', 'People'].each do |word|
  print "#{word} "
end
print "\n"

או כך:

['I', 'See', 'Dead', 'People'].each_with_index do |word, idx|
  puts "[#{idx}] #{word}"
end

וזה יפה כי המשמעות היא שפעולת סריקת אוסף היא מתודה של האוסף, ומי שכותב מחלקה למשל עבור שמירת נתונים בעץ יכול כחלק מהגדרת המחלקה להוסיף גם פעולות סריקה עליה בצורה שתשתלב טוב בשפה.

המחלקה CSV לדוגמא מייצגת קריאה מקובץ CSV. הקוד הבא מדפיס את העמודה הראשונה בקובץ בלולאה:

require 'csv'
CSV.foreach("file.csv") do |row|
  puts row[0]
end

הלולאה CSV.foreach מוגדרת כחלק מהגדרת המחלקה CSV, ומרגישה מאוד דומה לקוד רובי טבעי שכתבנו עד עכשיו.

אגב, רובי שפה ענקית ובין השאר כוללת גם פקודות לביצוע לולאות כמילים שמורות בשפה (המילים for, while ו until). בזכותן אפשר לממש את המתודות שהוצגו.

3. תנאים

הפקודה if של רובי מאוד דומה לפקודות תנאי בשפות אחרות ולכן לא מפתיע לגלות שהתוכנית הבאה ממתינה לשם נתיב מהמשתמש ובודקת אם הנתיב הוא קובץ או תיקיה.

print "File Name: "
fname = gets.chomp

if File.file?(fname)
  puts "#{fname} is a file"
elsif File.directory?(fname)
  puts "#{fname} is a directory"
else
  puts "Don't know nothing about #{fname}"
end

אגב קלטתם שאפשר לסיים פונקציה בסימן שאלה כן? ברובי סימן שאלה וגם סימן קריאה הם חלק משם תקני לפונקציה, וזה הרבה פעמים מהווה תחליף לתחילית is עבור פונקציות שמחזירות ערך אמת או שקר.

4. פונקציות

פונקציה מוגדרת על ידי המילה השמורה def, מחזירה את התוצאה של הביטוי האחרון בה ומגדירה איזה קלטים תקבל. יש תמיכה מלאה בערכי ברירת מחדל לפרמטרים, בהעברת פרמטרים לפי שם וב Variadic Functions. דוגמאות? בכיף. הפונקציה הבאה מקבלת טקסט ומספר ומדפיסה את הטקסט מספר פעמים לפי המספר שהועבר:

def print_times(text = 'Hello', n = 7)
  n.times do
    puts text
  end
end

print_times
print_times('hello world')
print_times('just saying...', 3)

הפונקציה הבאה מקבלת שני מספרים ומחזירה את סכומם:

def plus(a, b)
  a + b
end

puts plus(2,5)

ובגירסא היותר מתוחכמת, הפונקציה מקבלת כל מספר פרמטרים ומחזירה את סכומם:

def plus(*numbers)
  sum = 0
  numbers.each do |i|
    sum += i
  end

  # Or the shorter version:
  # numbers.inject(&:+)

  sum
end

puts plus(10, 20, 30, 40)

נחמד לראות שאין צורך במילה return בסוף הפונקציה מאחר וממילא תוחזר תוצאת הביטוי האחרון. סימן הכוכבית לפני שם משתנה מציין שהמשתנה יכיל מערך של ערכים שהועברו וכך הפונקציה יכולה לקבל מספר משתנה של פרמטרים.

5. מחלקות

מחלקה ב Ruby מתחילה במילה השמורה class, כוללת את כל הפונקציות שמוגדרות בתוכה בתור מתודות ויכולה להגדיר פוקנציית בנאי דרך פונקציה עם השם המיוחד initialize. המשתנה המיוחד self בתוך גוף מתודות מתיחס לאוביקט הנוכחי מהמחלקה (ובשונה מפייתון, אין צורך להגדיר אותו בשורת הפרמטרים של כל מתודה).

שדות מידע באוביקט (Member Variables) הם משתנים מיוחדים ששמם מתחיל בסימן @. בשונה מפייתון, אין אפשרות פשוט להוסיף שדות מידע על אוביקט self באמצעות כתיב הנקודה.

כך זה נראה במחלקה לדוגמא בשם Critter:

class Critter
  def initialize(name)
    @name = name
  end

  def eat(food)
    puts "Yummi! #{@name} loves #{food}"
  end
end

c = Critter.new("bob")
# prints: Yummi! bob loves bananas
c.eat("bananas")

רובי מאפשרת לקרוא לפונקציות בתוך הגדרת המחלקה. המנגנון משמש למשל ליצירת getters ו setters באופן אוטומטי, כמו בדוגמא הבאה:

class Critter
  attr_accessor :name

  def initialize(name)
    self.name = name
  end

  def eat(food)
    puts "Yummi! #{name} loves #{food}"
  end
end

c = Critter.new("bob")
puts "Hi! I'm #{c.name}"
# prints: Yummi! bob loves bananas
c.eat("bananas")

כתיב כזה הוא קצת יותר בטוח מהגירסא הקודמת כי השמה לשדה מידע אפשרית רק אם הוגדר attr_accessor עבורו.

6. מה עושים עם זה

רובי היא שפה שמאוד כיף לפתח בה. כאן בפוסט הראיתי רק ממש את ההתחלה של תחביר השפה. הפרויקטים המעניינים הם כמובן אלה שלוקחים את השפה ומאפשרים לבנות אתה דברים מתוחכמים.

אולי הספריה המפורסמת ביותר לרובי היא Ruby on Rails, המאפשרת פיתוח יישומי Web מורכבים בשפת Ruby. יש ספר מאוד מקיף וחינמי על ריילס בקישור כאן.

ספריה נוספת שיכולה לעניין היא Ruby Motion, המציעה דרך לפתח יישומי Mobile חוצי פלטפורמות ברובי. אפשר לקרוא על הספריה ולכתוב את היישום הראשון שלכם באתר הספריה בקישור כאן

ואחת האהובות עליי היא ספרית Selenium והחיבור המאוד פשוט שלה ל Ruby. ספריה זו מאפשרת לכתוב בדיקות אוטומטיות לאתרי אינטרנט כך שאתם יודעים בדיוק אם ומתי האתר שלכם נשבר. דוגמאות והסברים זמינים בקישור כאן