Symbols, Strings, Methods, and Variables

by Austin Ziegler

Both Jim Weirich and Yohanes Santoso posted about Ruby’s Symbol class today. As Yohanes noted, there’s not many weeks that go by where there’s no question about Symbols. I thought I’d throw in my two cents, expanding on a mailing list response ([ruby-talk:172842]). First, let’s look at what ri has to say about Symbol objects:

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.

The beginning of the confusion about Symbols being like Strings is straight from the source in this case. As both Yohanes and Jim point out, though, Symbols shouldn’t be used as "Strings-lite". At a minimum, to be useful that way, you’d have to convert each Symbol into a String (#to_s) before you could operate on it. If you shouldn’t use Symbols as if they were immutable strings, what good are they? Yohanes covers the theory well: helping to express intent. Jim covers the practical reasons well:

  1. Naming keyword options in a method argument list.
  2. Naming enumerated values (e.g. like enums in C).
  3. Naming options in an option hash table.

As Jim says, “Symbols are about naming and identifying things.” I don’t quite agree with his definition (“A Symbol is an object with a name”), preferring to say that a Symbol is an object that is a name. It’s a very subtle difference, mind you, but an important one in that there is no meaningful distinction between a Symbol and the name that it represents. That said, we’re still left with the question asked by Steve Litt, “One thing—why not some_call(:@my_variable)?”

This is where Yohanes’s discussion on intent matters. You aren’t naming your variable when you call attr_accessor. You’re naming your method. The magic here isn’t in the Symbol; it’s in attr_accessor.

>> class Foo
>>   attr_accessor :bar
>> end
=> nil
>> baz = Foo.new
=> #<Foo:0x2d8aea8>

Thus far, baz has no instance variables. But it does have two instance methods:

>> baz.methods - Object.methods
=> ["bar", "bar="]

If I call the reader method (Foo#bar), I still don’t get an instance variable:

>> baz.bar
=> nil
>> baz
=> #<Foo:0x2d8aea8>

It’s only when I call the setter method (Foo#bar=) that my instance variable is created:

>> baz.bar = 32
=> 32
>> baz
=> #<Foo:0x2d8aea8 @bar=32>

The intent of the Symbol is that it’s just a name. The intent of attr_accessor is that it creates two methods for each name that it’s been given. It is almost coincidental that these methods work on a variable of the same name. It doesn’t have to be the same, as I demonstrate below.

require 'digest/md5'
class Module
  def md5_accessor(*names)
    names.each do |name|
      var = Digest::MD5.hexdigest(rand(65536).to_s)
      define_method(name) { || instance_variable_get("@_#{var}") }
      define_method("#{name}=") { |v| instance_variable_set("@_#{var}", v) }
    end
    nil
  end
end

class Foo
  md5_accessor :bar, :baz
end

moo = Foo.new
moo.bar = 5
moo.baz = 7
moo
puts moo.bar, moo.baz

1 Comments

Michael Kovacs
2006-01-03 08:11:51
I also blogged about the use of symbols but this time in rails and how newbies can easily be tripped up.