Ruby is a silly programming language. I’m not trying to start a flame war, and this isn’t really a rant. There is a lot I like about Ruby, and it gets more right than most any other language. But it has it’s idiosyncracies, and here are some of them:
I’ll start with the simplest thing to pick on, syntax. While everyone has their own preferences and in part it depends on what you’re accustomed to, there are some objective judgments that can be made.
My personal pet peeve is the function definition keyword def, which is short for ‘define’. Define what, though? A function, of course. While ‘def’ might be preferable to ‘fun’, it is less descriptive. Why not use ‘def’ to define a class, if everything is an object? This is a relic from earlier languages when functions were the only abstraction, thus the only thing you could define. That was so long ago that there was only enough memory for three letter commands.
Then there is the iterator syntax. In the name of making code easier to read, you get idioms like 5.times do … and fishes.each do … Interesting, but nothing at all like English. You might suspect that since Matz is Japanese, that it might match the sentence structure of that language — but it doesn’t. The words are English anyway. If the phrasing is pidgin, then it’s even more silly.
Which brings me to do…end. Why take away the braces? It used to be the accepted way to group blocks of code (and is still allowed for unnamed blocks) but there was also the option of using ‘do…end’. Braces were then discouraged, then banned. All in the name of readability but, as pointed out already, that’s not readable in English. Not only does it take more keystrokes (and storage space, and screen real estate), it is also distracting and makes parsing the code more difficult, both for the interpreter and more so for your eyes. Also, ‘do’ is almost always superfluous.
There are a lot of silly function names in Ruby. You can bash PHP for function names like ‘strnicmp’ but that is actually following a convention that has a 30+ year pedigree. But in Ruby, for example, sub is used for ‘substitute’ and gsub for substitute all. But those are the venerable (thankfully retired) words first used to define and invoke functions (subroutines). Which reminds me that ‘end’ is also a keyword with a past that has a more accurate meaning. Using puts as a synonym ‘print line’ is another example. ‘Puts’ means ‘put to the screen’, but Ruby uses ‘puts’ for standard IO. And while it uses getc to get a character, puts and gets read lines, not strings. ‘put’ and ‘get’ would be better, and also more in line with traditional usage.
Blocks are nothing more than unnamed functions. While many Ruby fans think they are the greatest thing since sliced bread, there are several disadvantages to blocks. The first is the potential for inefficiency. It is easy to get in the habit of passing around blocks, and algorithms in blocks. Because blocks are first class objects, it is not the same thing as passing around function pointers, although they are passed by reference. Which means they have identifiers on the heap/stack/whatever, anyway.
Unnamed blocks are great, occasionally, for 1 liners. But do you then really need a block? By which I mean, do you need to pass a block for execution?
Another problem is that because blocks are not named, if they have much practical use, you start cutting and pasting blocks. That flies in the face of “Don’t Repeat Yourself”, and can lead to maintainability nightmares.
Because blocks are unnamed, they are undocumentable. The first and most important part of documenting a subroutine (which is what a block is) is giving it a name. A descriptive name is good, though I realize that names that describe what many blocks do would end up being longer than the code block itself, which is a succinct description of what it does already. And because they are not isolated, they cannot be described in their definition as objects, even though that’s what they are.
Blocks wouldn’t be much use without iterators. In Ruby, iterators have a different syntax than other languages. Objects have built in iterators, which is sometimes nice, but with the cost being that it makes objects much heavier. Even simple integers carry this weight around with them.
A significant performance (and memory usage) improvement could be achieved in Ruby just by separating iterators from base objects. Because of their baggage however, almost no operations that require any degree of efficiency can be written in Ruby.
For most objects, you end up implementing your own iterators anyway, and since you might need multiple ways to iterate, a single solution often isn’t useful.
If you want to have collections that are iterable, by all means have them, but don’t pretend that arrays are iterable. That’s a job for lists.
Give me the choice of having expensive functionality (of which iterators are just one example) or not, so I can write more efficient code if I want. And give me transformations (methods or casts) so I can get at the base array, hash, or collection beneath the iterable, sortable behemoth.
[Side note: I don’t know of any languages where casts are first class objects, but that could be an interesting idea]
Closures are sometimes neat, but you’re just passing around blocks and iterating lists. The same thing can be done almost as easily iterating over regular lists and calling functions.
Symbols are a really clever way to avoid the deadweight in Strings. Symbols are useful in their own right, but it’s really a workaround for the overweight object that Strings have become in Ruby.
Everything is an Object
There really are basic constructions below the level of an Object. They exist in Ruby but the language, by design, makes it difficult to access them. While it’s useful to think of everything (like numbers, blocks, and class definitions) as objects, it doesn’t make sense to have them all descend from the same base Object.
Mixins are just global (package/namespace scoped) functions. It is sometimes a handy abstraction, and since functions are first class objects, there is no OOP religious reason to not allow them. Weak “duck” typing makes it possible to pass unknown types, but also makes it brittle, except with simple types that are easily cast such as String, Integer, Float, and Character.
A few miscellaneous points:
Why single line comments only?
Variable scope sigils: @, @@, $ and none. I can’t think of a single reason for the variety except to avoid implementing scope tracking in the interpreter. Making scope self documenting by enforcing it with syntax is debatably beneficial, but it is a strange arbitrary selection. I’d vote for consistency first.
Conventions which are almost compiler rules: CONSTANT, boolean?, change!, assign=
Capitalized True and False.
Implicit return values. Not all subroutines return values, or always return values. By implicity returning the last lvalue, you could potentially return the wrong thing.