I took my first look at Ruby today. It's not imperative that I learn it right away. We're actually developing in a few different languages but I'm sufficiently conversant in the others (C++ and Java) that I'm confident I could be up and running with any of that code quite quickly. Ruby is the dark horse at the moment. But it's been pushed quite hard by a number of people (some of whom I even trust) and it really has garnered quite a bit of press lately. So I've actually been toying with the idea for a while now.
So far though, I'm less than impressed. There's a distinct lack of any warm fuzziness. I'll be the first to admit, though, that I've only scratched the surface. So anything written here should be read bearing that in mind. And this is probably coloured by the usual frustration of learning a new language: having to look every second thing you want to do up. This will pass as I start to get inside of the mind of the Ruby author (ouch) and is something I've experienced with every language I've picked up (save Perl oddly enough, which is probably a pretty serious indictment). With all that said, let's take a closer look at some of the "features" Ruby has to offer.
Everything is an object: This is usually one of the first things Ruby pimps raise. Frankly, so what? So everything's an object? How does that benefit you? In languages like C# and Java this applies to enough of a degree that the difference is immaterial for most practical purposes. Yes, blocks and closures are objects but this smacks of delegates (so C# can do this too). Interfaces are a much safer way of managing callbacks. And if you really want delegates in Java then you can always abuse the reflection API and build a delegate of your own using an instance of the Method class. But, cry fans of the language, you can create arbitrary blocks and they're treated as objects too. Uh huh. This means you can do this:
myblock = {
puts "Look ma, no hands!"
}
doSomethingWith(myblock)
Woohoo. Not exactly a major win in my books. Ah, but Ruby lets you do all of this in less code than Java or C#. So? How often is this actually a good idea? In code that will live on for a decade? In code that needs to be manhandled by 50 programmers? 500? 5000? Most "productivity enhancements" I'm seeing touted at the moment save typing. This is not a productivity boost unless you're the only person working on your code and it never gets over a few thousand lines long. Code is
read far more than it's
written. Even taking into account changes as code is fixed or evolved. More eyeballs will spend time trying to figure it out than fingers will
ever spend writing it.
Closures: I'm not sure what it is about lexical closures that seems to have made them the central figure in every second programmer's nightly wet dream. For those with clean sheets closures are basically anonymous blocks that "capture" the context they're defined in and can be executed by a caller elsewhere (or elsewhen). Frankly I haven't yet seen a compelling argument for this. And don't say callbacks. All you need for that is either something along the lines of interfaces, or delegates (or function pointers). Capturing things defined within the local scope of the closure is useful for parameterizing the logic you're about to throw all over the place, but again, this isn't something you can't do with an anonymous inner class or a delegate.
One of the big things pushed here seems to be that there's far less syntax involved. This is supposed to make it more likely that you'll use them. I don't believe in designing a language around this kind of argument. Frankly, the IDE should take care of that kind of thing. If your language design tries to compensate like this you end up with write-only code because you go out of your way to reduce perceived verbosity. This usually means favouring all sorts of implicit nonsense (Perl is famous for this) and you start using punctuation instead of keywords (again, Perl took this to a new level, but C++ had a crack at it too). More on this later. The big push in the docs I've read so far around closures seem to be related to working with collections. Essentially you pass in a closure that operates on each item in the collection. Nice idea, except that most real world examples I can come up with need to modify the collection (filter it, reorder elements, etc). All of the examples I've seen so far seem to operate on the element alone, which usually means printing it. If you want to modify the collection, or produce a new one, then the closure needs access to something defined outside of the closure (like a new collection to put the filtered elements into). Fortunately closures can do this, but suddenly the caller needs an idea of what your closure is doing. It stops being a black box, which usually means it's usefulness decreases by an order of magnitude. Basically it turns into a (hard to maintain) toy. In the delightful syntax of Ruby you end up with something like this:
filtered_list = []
somelist.each { |item| filtered_list << item if (item == somevalue) }
The above is the "Ruby way" of doing things. The introductory docs I'm reading pimped the
each method about half a dozen times within the first few paragraphs. Exciting stuff. Frankly:
for item in list:
doCrap(item)
is far more natural (and reads easier) than the "Ruby" way of doing this:
list.each { |item| doCrap(item) }
Which brings me swiftly to my next point.
A simple syntax. Really? I've yet to actually encounter anything in Ruby that feels intuitive. Maybe this will change after a few days spent scratching my own eyes out. Ruby's full of little gems like the << operator above. Or the pipe operator for defining parameters for closures. And the "simple naming convention denotes the type of a variable" nonsense slays me: var is a local, @var is an instance member, @@var is a class member and $var is a global. The argument put forward seems to be "no more typing self or this". I've never once caught myself thinking "Damn, I wish I didn't have to type this. If only the language could do away with it in favour of an obscure naming convention". It gets better: method names have to begin with a lowercase letter. This isn't just a convention. The interpreter actually depends on these things and may parse your code incorrectly if you deviate. Sheesh. And then, just in case you're still climbing the hill, they go ahead and allow two punctuation marks (? and !) as part of a method name (with the convention that the former be used for "query" methods and the latter for "dangerous" methods or methods that modify their parameters). This is probably personal preference, but I don't like punctuation as part of an identifier name. We're trained from birth to split text on punctuation marks. Ruby's not going to undo that single-handedly.
It can't decide whether it's Python or Perl and it seems to have gone out of it's way to attract Perl programmers, even going so far as to propogate nonsense like implicit globals (badly named ones at that) like $_ for intermediate storage. Again, this is touted as a "feature" for the same reason Perl introduced it: it lets you do more with less typing. Which is usually another way of saying "lets you write unmaintainable code".
Ruby has also decided (like Perl before it) that one way to do things isn't enough. Perl made this popular, based on the idea that ten programmers will write the same piece of code ten different ways. The logic goes something like "well, if we can express the same thing in ten different ways then any given programmer is more likely to find the language a natural fit with his way of thinking". What it translates into is ten different programmers doing things ten different ways, producing large amounts of what a friend called "write-only" code.
And what's with puts? Why the hell does every new language feel the need to change something simple (and nearly universal) like the name of the routine that prints something to stdout? We've had printf, writeln, println, print and puts. Fucking choose one. Why does every language have to be different? This isn't a Ruby specific rant. It annoys me that switching languages has me fumbling with basic things instead of focusing on the real differences. With Ruby we have print and puts. The former doesn't include a line terminator, while the latter does.
Dynamic typing kicks your tools in the nuts. Goodbye auto-completion. Goodbye refactoring support. Want to rename a method? %s/oldname/newname/g and lots of testing. And that assumes its in a single file and there are no name collisions. Ruby has turned Eclipse into a bloated version of notepad. As far as editing Ruby code is concerned, it's currently about as useful as Microsoft Word.
And there are all the other little things that so far haven't endeared Ruby to me. The interpreter is about as useful as Python when it comes to error reporting. It'll just about point out the line of code that it doesn't like and tell me there's a syntax error. (Somewhere there. Left, left. Right. Yes, there! What? No, I'm not going to tell you what's wrong). And even at version 1.8.2 we're seeing stability issues, which isn't going to win friends.
It's not all bad. I like the native regular expression support (much like Perl's), although I think it lends itself to abuse just as it did in Perl. But overall I'm not blown away. And I certainly don't feel like I'm working in a language with a cleaner syntax. Java made C++ syntax feel overly-complicated. Ruby's done nothing of the sort, for any languages I know.
Hey, nowhere to go but up. Right?