• בלוג
  • משתנים ופונקציות גלובאליים ברובי

משתנים ופונקציות גלובאליים ברובי

16/09/2019

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

1. הגדרת משתנים ופונקציות באותו קובץ

נגדיר קובץ בשם utils.rb עם התוכן הבא:

def twice(x)
    x * 2
end

value = 10

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

2. איך פונקציות ומשתנים נראים מבחוץ

נתחיל עם המשתנה הפרטי - טעינת הקובץ וניסיון לגשת למשתנה נכשלת:

> require './utils.rb'
> value
Traceback (most recent call last):
        4: from /Users/ynonperek/.rvm/rubies/ruby-2.6.3/bin/irb:23:in `<main>'
        3: from /Users/ynonperek/.rvm/rubies/ruby-2.6.3/bin/irb:23:in `load'
        2: from /Users/ynonperek/.rvm/rubies/ruby-2.6.3/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
        1: from (irb):2
NameError (undefined local variable or method `value' for main:Object)

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

 > require './utils.rb'
 > twice(10)
  => 20

וזה הצליח! אבל רגע, מה לגבי ההודעה ההיא עם המתודה של Object? נסתכל רגע על האוביקט:

> Object.private_instance_methods
 => [:DelegateClass, :twice, :irb_binding, :sprintf, :format, :Integer, :Float, :String, :Array, :Hash, :srand, :warn, :local_variables, :trap, :abort, :require, :rand, :raise, :fail, :global_variables, :__method__, :__callee__, :__dir__, :binding, :require_relative, :autoload, :autoload?, :eval, :iterator?, :block_given?, :catch, :throw, :loop, :system, :spawn, :exec, :exit!, :trace_var, :untrace_var, :at_exit, :select, :`, :Rational, :gem_original_require, :Complex, :set_trace_func, :pp, :gem, :respond_to_missing?, :test, :caller_locations, :caller, :fork, :exit, :gets, :proc, :lambda, :initialize_copy, :initialize_clone, :initialize_dup, :URI, :sleep, :load, :syscall, :open, :printf, :print, :putc, :puts, :readline, :readlines, :p, :singleton_method_added, :method_missing, :singleton_method_removed, :singleton_method_undefined, :initialize] 

וכבר הפונקציה השניה ברשימה נראית מוכרת! זו twice שלנו. הפונקציה הגלובלית היא בעצם מתודה פרטית של האוביקט Object. תופעת לוואי מעניינת של מבנה זה היא שעכשיו אפשר להפעיל פונקציה זו דרך כל אוביקט רובי. הנה המשך העבודה מ irb:

> l = [10, 20, 30]
 => [10, 20, 30] 
 > l.send(:twice, 10)
  => 20 

חיפוש שם של משתנה או פונקציה ברובי מתחיל בחיפוש משתנה מקומי בשם שכתבנו וממשיך בחיפוש מתודה בשם זה בתוך ה Scope הנוכחי, ומשם ממשיכים לחפש במעלה ה Scope Chain עד שמגיעים ל Object. בתוך מגבלה זו של השפה הדרך היחידה של פונקציה להיות "גלובאלית" היא להישתל בתור מתודה של Object, כך שמכל אוביקט אפשר יהיה להפעיל אותה.