|
[edit] Why Symbols?
Before we can say what symbols are or why we even need them, we need to talk a bit about mutable and immutable objects. Immutable objects have a frozen state, i.e. the object's content or internal memory representation cannot be changed once the object has been initialized. Immutable objects are generally a good idea. They are especially good for parallel processes since evils like race conditions are eliminated. Also, since the object state cannot change, the same object can be shared between multiple variables without worrying about changes done through one variable propagating to other variables because they happened to name the exact same mutable object. Also, sharing an object between multiple names leads to extensive savings in memory consumption. Mutable objects can have their state changed at any time. I will quickly illustrate the idea with an example, first using mutable strings and then immutable strings.
[edit] Mutable strings:
a = "hello"
b = "hello"
c = a
The variables a and b point to completely different objects which have different addresses in memory. The objects just happen to have the same internal memory representation or content. The variables a and c point to the same object though, but since the strings are mutable, any change in the state of that object will reflect both in a and c. This is why a and b have to be different objects or the shared state would cause problems unless an explicit copy of the string is made. Ruby strings are mutable so you have to be a bit careful so that such a situation does not arise.
a[0] = 'H'
puts a # prints Hello
puts c # prints Hello
[edit] Immutable strings:
Using the same code snippets as above, variables a and b can be different objects but they really don't have to be. The string cannot be changed after it has been created so there is no need to create extra copies of it. No matter how many string objects we create with the same content, they can all share the same object thus affording as huge savings in memory consumption. You can make any object in Ruby immutable by invoking the freeze method on that object.
x = 'abc'
x.freeze
x[0] = 'A' # Error!
Now in Ruby, Strings are mutable. To give us something more like immutable strings, Ruby has Symbols.
Symbols
Ruby gives us symbols which are pretty much the same thing as immutable strings. Interpreters and compilers use something called a symbol table for quickly looking up names of variables, function, classes and so on and so forth. The symbol table is typically implemented using a hash table and so allows constant time lookup of objects associated with names. Ruby interpreter is no different but it does go a bit further by exposing the symbol table to the programmer through the Symbol object and some syntactic sugar. Any identifier or string prefixed with a colon is a Symbol object. The Symbol objects live in the interpreter's symbol tables so they can be looked up quickly. Here are some examples.
:hello # A Symbol literal
:"hello" # Same as above. Even maps to the same Symbol object
:'ruby rocks' # Use quotes for symbols with spaces
[edit] Advantages of Symbols
Symbol objects have two key advantages. They live in Ruby interpreter's symbol table and are stored as an integer so they can be looked up instantly. Comparing two integers is way faster than comparing two strings character by character. They are immutable and so a single symbol object can safely be shared between multiple names or variables. This obviously leads to efficient use of memory since the maximum space taken by a symbol is never more than the space taken by an integer. Each time a string is used in a program, a new copy of the string is created. Not so with symbols as can be seen from the code below.
puts "name".object_id # prints some id X
puts "name".object_id # prints some id Y
puts :name.object_id # prints id Z
puts :name.object_id # prints the same id Z as above
Symbols are often used to refer to method names in code that uses metaprogramming or reflection. For example, if we want to check whether an object responds to the each method, we can do the following.
o.responds_to? :each # check if doing o.each is legal
Notice that :each is a Symbol object. No matter how many times we use :each in our program, the same Symbol object will be used. Remember the two key advantages, quick lookup and a single copy of the Symbol object. Good stuff!
|