Christoph Schiessl's Blog

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:

1 describe do
2   let(:h) { { "foo" => "bar" } }
3   let(:a) { [h] }
4   it { a.should =~ ["foo" => "bar"] }
5 end

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:

1 ~  irb
2 2.0.0-p247 :001 > [{ "foo" => "bar" }] == ["foo" => "bar"]
3  => true

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

1 2.0.0-p247 :002 > [123, { "foo" => "bar" }] == [123, "foo" => "bar"]
2  => true
3 2.0.0-p247 :003 > [{ "foo" => "bar" }, 123] == ["foo" => "bar", 123]
4 SyntaxError: (irb):3: syntax error, unexpected ']', expecting =>
5   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:

1 2.0.0-p247 :004 > self.method(:[])
2 NameError: undefined method `[]' for class `Object'
3   from (irb):8:in `method'
4   from (irb):8
5   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:

1 2.0.0-p247 :004 > Array[]
2  => []
3 2.0.0-p247 :005 > Array[123]
4  => [123]
5 2.0.0-p247 :006 > Array[123, "bar" => "foo"]
6  => [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:

 1 2.0.0-p247 :007 > class <<Array
 2 2.0.0-p247 :008?>     alias_method :original_square_brackets, :[]
 3 2.0.0-p247 :009?>     def [](*args)
 4 2.0.0-p247 :010?>         puts "Array.[] has been called!"
 5 2.0.0-p247 :011?>         original_square_brackets(*args)
 6 2.0.0-p247 :012?>       end
 7 2.0.0-p247 :013?>   end
 8  => nil
 9 2.0.0-p247 :014 > Array[1, 2, 3]
10 Array.[] has been called!
11  => [1, 2, 3]
12 2.0.0-p247 :015 > [1, 2, 3]
13  => [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.

Conclusion

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"]?

comments powered by Disqus