Some Mental Traps in Learning Ruby
talklikeaduck.denhaven2.com
talklikeaduck.denhaven2.com
In fact, this is one of the "features" of Ruby that I hate -- it's frequently difficult to know what a particular piece of code does, because the Ruby community's golly-gee fascination with interfaces and dynamic mixins tends to lead to inconsistent behavior in different contexts.
The idea of separating interface and implementation is not new. Even us crusty, abused C++ programmers can override the bracket operator; we just happen to believe that overriding that operator in a way that leads to unexpected behaviour for particular subscript values is a Bad Idea.
The principle of least surprise is an important one in programming; I wish that more people would obey it as a law.
To know which method is called, you need to know the class of the receiver and the method name - together they determine unambiguously which method is called, and hence there is no surprise.
The only surprises occur when the programmer is mistaken about the type of an object - those are the risks of dynamically typed languages.
I understand that [] is a method in Ruby. In fact, operator[] is also a method name in C++, and can be overloaded, overridden, etc. Ruby did not invent these features. Likewise, in C++, the type of an object and the method name invoked determine unambiguously the method that is called.
The difference -- and this is critical -- is that in the C++ community, there is a strong bias against syntactical games that make code behave inconsistently. Having operator[] behave differently for different subscripts would be a mortal sin in C++ design (not to mention that the type system would probably punish you for it).
We can debate the relative merits of strong/weak typing, but the principle of least surprise is a good one, and should be respected, regardless of what a language may allow you to do (cf. Perl).
Also in Ruby there is no overloading. Overloading means that a different method is chosen depending on the types of the arguments. That means that in C++, it is not true that "the type of an object and the method name invoked determine unambiguously the method that is called".
In practise there is also little overriding - there seems to be no need. In C++ there is a lot more, and because of multiple inheritance, things can get confusing. (Overriding and overloading are two ways in which OO-languages violate the principle of least surprise).
This means that the reasons why using operator overloading is bad in C++ don't (automatically) apply to Ruby.
Every "experienced Ruby programmer" may well come to understand that standard expressions fail to mean standard things when used in Ruby; that doesn't make it a good idiom. The problem here isn't that the bracket operator fails to reference into an array -- it's that the bracket operator might behave inconsistently for certain subscript values.
The STL map class is a perfect analogue: rather than implement an operator[] than could fail in certain contexts, the C++ standard demands that map::operator[] must always return an object. This is consistent behavior. Allowing operator[] to fail for subscript N-1 when it works for N? That's inconsistent (and confusing), but apparently, it's the Ruby Way.
Unless someone's been using metaclass trickery, eg.
array = []
def array.length; "banana"; end
array.length # => "banana"
[].length # => 0
(you can do the same thing with []=, the example is just longer)Also, I think this would count as a mortal sin in production code (but a lot of fun nevertheless).
There seems to be some Ruby communal understanding that the use of [x] should follow Array-ish behavior, but when used with a class constant it's apparent that this is not simply an array, and that some other sort of indexing is going on.