Project Inquiry
Published on

About Instantiating Ruby Arrays Using Literal Notation

Last week, I discovered an oddity about Ruby’s literal array syntax in conjunction with hashes. The following (simplified) test is syntactically valid Ruby code:

describe do
  let(:h) { { "foo" => "bar" } }
  let(:a) { [h] }
  it { a.should =~ ["foo" => "bar"] }

Do you see what’s strange about the syntax? After running the test, I was about to add a second expectation and noticed the missing curly braces in line number 4. As it turns out, the following statement evaluates to true:

2.0.0-p247 :001 > [{ "foo" => "bar" }] == ["foo" => "bar"]
 => true

For me, this was a surprise, since I would have expected some kind a syntax error (maybe unexpected token '=>'). Further experiments revealed the following:

2.0.0-p247 :002 > [123, { "foo" => "bar" }] == [123, "foo" => "bar"]
 => true
2.0.0-p247 :003 > [{ "foo" => "bar" }, 123] == ["foo" => "bar", 123]
SyntaxError: (irb):3: syntax error, unexpected ']', expecting =>
  from /Users/cs/.rvm/rubies/ruby-2.0.0-p247/bin/irb:16:in `<main>'

Literal array definitions, seem to behave exactly like ordinary method invocations: The last argument is interpreted as a hash – even without the curly braces. In the Rails community, this behaviour is widely known as the options hash.

Now, every Ruby method call must have a receiver. If no explicit receiver has been specified, the implicit receiver is self. This suggets, that there should be a method [] defined on self. Let’s check:

2.0.0-p247 :004 > self.method(:[])
NameError: undefined method `[]' for class `Object'
  from (irb):8:in `method'
  from (irb):8
  from /Users/cs/.rvm/rubies/ruby-2.0.0-p247/bin/irb:16:in `<main>'

Strangely, [] is not defined. Hmmm.

By consulting the standard library documentation, I disovered the method Array.[]. Except for the explicit receiver, that class method seems to be functionally equivalent to the literal syntax:

2.0.0-p247 :004 > Array[]
 => []
2.0.0-p247 :005 > Array[123]
 => [123]
2.0.0-p247 :006 > Array[123, "bar" => "foo"]
 => [123, {"bar"=>"foo"}]

Just out of curiousity, it would be interesting to find out wether Array.[] gets called when instantiating a new array with literal syntax. Luckily, Ruby’s meta-programming features make it easy to rewrite existing methods:

2.0.0-p247 :007 > class <<Array
2.0.0-p247 :008?>     alias_method :original_square_brackets, :[]
2.0.0-p247 :009?>     def [](*args)
2.0.0-p247 :010?>         puts "Array.[] has been called!"
2.0.0-p247 :011?>         original_square_brackets(*args)
2.0.0-p247 :012?>       end
2.0.0-p247 :013?>   end
 => nil
2.0.0-p247 :014 > Array[1, 2, 3]
Array.[] has been called!
 => [1, 2, 3]
2.0.0-p247 :015 > [1, 2, 3]
 => [1, 2, 3]

Line number 9 clearly demonstrates that Array.[] has been rewritten as intended. However, nothing has been printed during instantiation of the literal array in line number 12.


Even though, [] is behaving exactly like Array.[] (which is an ordinary method), it’s not possible to change []. In comparision, Array.[] can be overwritten easily. My guess is, that the interpreter has been optimized to directly use the native implementation of [] and skip the usual method lookup process. Magic ;)

Did you know about [{"foo" => "bar"}] == ["bar" => "foo"]?