סיור מהיר בשפת Ruby

21/01/2018

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

1. שלום עולם

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

puts "Hello World"

נו זה לא היה קשה. הפקודה puts מדפיסה על המסך ולכן התוצאה היא הדפסה הטקסט Hello World.

אגב את התוכנית אפשר להריץ משורת הפקודה באמצעות:

ruby hello.rb

או להתקין את סביבת הפיתוח RubyMine (בתשלום) שמציעה את כל הפינוקים שאנחנו מכירים מ Jetbrains. בגזרת הקוד הפתוח יש את Arcadia שגם כתוב ברובי.

2. כל דבר הוא אוביקט

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

if 16.even?
    puts "16 is even"
else
    puts "16 is odd"
end

מלבד העובדה שאפשר להפעיל את המתודה even? על המספר 16, מוזרות נוספת היא שסימן שאלה הוא תו חוקי לגמרי בשם של פונקציה (ואגב גם סימן קריאה).

3. כל ביטוי מחזיר ערך

ב Ruby לכל ביטוי יש ערך, לכן פונקציה מחזירה אוטומטית את תוצאת הביטוי האחרון בה. ערך ההחזר של if הוא הענף שבוצע ובלולאה אנחנו קובעים מתוך גוף הלולאה מה יהיה ערך ההחזר. אגב הפקודה gets קוראת קלט מהמשתמש, והפקודה to_i הופכת אותו למספר. זה אומר שהביטוי הבא חוקי לגמרי ומחליף את הטרנרי שיש בשפות אחרות:

num = gets.to_i

result = if num.even?
           num / 2
         else
           (num + 1) / 2
         end

puts result

4. שיערוך קוד בתוך מחרוזות

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

num = gets.to_i

result = if num.even?
           num / 2
         else
           (num + 1) / 2
         end

puts "#{num} / 2 rounded up is #{result}"

מדפיסה את התוצאה הבאה בהפעלה:

localhost:~ ynonperek$ ruby a.rb
10
10 / 2 rounded up is 5
localhost:~ ynonperek$ ruby a.rb
9
9 / 2 rounded up is 5

5. פונקציות יכולות לקבל פרמטרים בהרבה דרכים

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

אחד המבנים הראשונים שפוגשים הוא העברת "קטע קוד", נקרא ברובי block, לפונקציה:

5.times do |i|
    puts "Hello #{i}"
end

המתודה times של האוביקט 5 קיבלה כפרמטר את הבלוק שמתחיל במילה do ונגמר במילה end. אותה times יכולה להחליט מה לעשות עם הבלוק, ובמקרה של times היא מפעילה אותו מספר פעמים (5 במקרה שלנו), וכל פעם מעבירה אינדקס רץ. כן, בלוק יכול לקבל פרמטרים.

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

Hello 0
Hello 1
Hello 2
Hello 3
Hello 4

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

# true - there's a value in the array for which the block evaluates to true
 ['foo', 'bar', 'buz'].any? {|word| word.length == 3}

 # false - not all values starts with a 'b'
 ['foo', 'bar', 'buz'].all? {|word| word.start_with?('b') }

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

def times(n, &code)
  i = 0
  loop do
    break if i >= n
    code.call(i)
    i += 1
  end
end

times(5) do |i|
  puts "Hello World #{i}"
end

ויש גם קיצור דרך שנקרא yield שחוסך לציין את הבלוק ברשימת הפרמטרים. הקוד הבא שקול:

def times(n)
  i = 0
  loop do
    break if i >= n
    yield i
    i += 1
  end
end

times(5) do |i|
  puts "Hello World #{i}"
end

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

def times(n, options = {})
  steps = options[:step] || 1

  i = 0
  loop do
    break if i >= n
    yield i
    i += steps
  end
end

times(5, step: 2) do |i|
  puts "Hello World #{i}"
end

הצמד step: 2 אוטומטית הופך ל Hash בשם options, כאשר המפתח :step ממופה לערך 2.

6. אגב סימן הנקודותיים

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

x = "hello"
y = "world"
if x == y
    puts "x and y are equal"
end

הסמל הוא משהו בין מחרוזת למספר. בקוד הוא נראה כמו מחרוזת, אבל למחשב זה נראה כמו מספר, ולכן השוואה בין symbols היא מאוד מהירה. רובי מנהלת טבלא בזיכרון שממפה בין כל Symbol לערך שלו (תחשבו על enum ענק), לכן לא מומלץ לקחת קלט חיצוני ולהפוך אותו ל Symbols.

אותה הדוגמא עם Symbols תראה כך:

x = :hello
y = :world
if x == y:
    puts "x and y are equal"

7. מה הלאה

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

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