Monday, September 19, 2011

Ruby Symbol Explained

Symbol is a bit confusing for Ruby learner to comprehend so if you happen to be one of us, don't be too worry as you will find out it's not as bad as it first looks.  


So, what is a symbol? According Ruby-doc,
Symbol objects represent names and some strings inside the Ruby interpreter. They are generated using the :name and :"string" literals syntax, and by the various to_sym methods. The same Symbol object will be created for a given name or string for the duration of a program‘s execution, regardless of the context or meaning of that name. Thus if Fred is a constant in one context, a method in another, and a class in a third, the symbol :Fred will be the same object in all three contexts.
What that means are:
  • If you prefix a string or an legal Ruby name/identifier with a colon, it becomes a symbol
    • x = :"Matt" # x.class is a Symbol
    • x.to_s() "Matt"
    • y = :userId # y.class is a Symbol
    • y.to_s() # "userId"
  • A symbol is an instance of Symbol class.  If you runs Symbol.public_methods(false), irb will return 3 methods: ["superclass", "allocate", "all_symbols"].  all_symbols() method returns a table of symbols currently bound by runtime Ruby.
  • Symbols are not strings because Symbol and String are 2 different classes.  You cannot therefore do the following:
    • Assign :user = "Sam"  # syntax error, unexpected '=', expecting $end
      • The correct way to assign a symbol is user = :"Sam"
    • Invoke String's method :user.size() # NoMethodError: undefined method `size' for user:Symbol
      • Correct way to get size of User symbol's value is :user.to_s().size()
  • Symbol's value, a string, is obtained via via Symbol#to_s() or Symbol#id2name() methods.  Vice versa, a string can be converted to Symbol via String#to_sym() or String#intern().
  • Symbol is immutable so unlike String, you cannot modify a string once assigned.  
    • user.to_s()[0] = "K" # changing first character of 'Sam' will not affect user.  Afterward, user.to_s() is still "Matt"
    • Bonus: you can make a string/array object immutable via freeze() method.  Vice versa, unfreeze() will allow string/array object to be editable.
Fine, you start to get impatient.  Then tell me why and where you want to use symbol?
  1. Symbol as a key in Hash's key instead of string.  Instead of having to refer to "id" string whenever rec["id"] is called, a good engineer would at least define a constant like static const String ID = "id in Java and always refer to rec[ID] instead.  Ruby symbol does exactly that.
    • rec = { id => :"101"; name => :"Matt Burnes"; email => :"mburnes@mclub.org" } # rec.class returns Hash
    • p rec[:id] # "101" 
  2. Save memory when replace a large number of identical strings.  Since symbol id is a reference to a same identical "id", you reuse same memory for identical "id" every time "id" string is referred.  I wish Ruby could be as smart as Java where it figures out if same literal string "id" is instantiated, any time it encounters same string, it will just reference it. 
    • id.object_id   # returns 303408
    • "id".object_id 23700936
    • "id".object_id 23689692 (new object)
    • "id".object_id 23485564 (new object)
    • id.object_id   # still returns 303408
Conclusion
  • Symbol is immutable.
  • Symbol is not a string.
  • Symbol is favored over String when same string is referenced repeatedly to save memory. 

No comments:

Post a Comment