I've broken up my response into bits so its easier to follow. Sorry this is a bit late but I can't formulate a worthwhile response while at work so things have to wait until I get home.
Before I begin, I just want to say that I don't think Ruby is a bad language. I just find it to be inconsistent and in disagreement with my own personal philosophies on programming language design.
Oh? Please do be a bit more specific regarding the horrible inconsistencies. "Certain 'features'" is an extremely generic way to begin a critique. Syntax is almost always entirely personal, but they both have something going for them in different ways. Whitespace-significance is awesome, obviously, but paren-less methods could be considered just as aesthetically pleasing to the right eyes.
You're right, syntax is a personal preference. In general, this is true, but syntax is also meant to force some structure onto the program such that it is readable to the programmer. Programs are more often read than they are written, so a readable syntax is a requirement. But what consists of a readable syntax is largely based on what a programmer is used to. Non-readable syntaxes can become readable syntaxes over time. Since you brought up parenthesis-less function calls, we'll discuss those. Take the following code, for example:
def foo(i)
return i - 1
end
puts foo 10 * 2
If I were to read this, I wouldn't immediately know whether foo will receive 20 as the parameter or will it receive 10, and then multiply the result by 2. It's only by trying it out in the interpreter that I can find out for sure:
>> def foo(i)
>> return i - 1
>> end
=> nil
>> foo 10 * 2
=> 19
Turns out that foo receives 20 as a parameter, and returns 20 - 1, 19 as the result. A construct like this could lead to programming errors that are really difficult to track down because the syntax doesn't help you any here. You could say that it's better to be explicit about things like this and put parenthesis around the ambiguous parts. But then why didn't we just stick to the tried and true convention of placing parenthesis around arguments?
It gets even more confusing when you have functions that don't take arguments and use them without parenthesis:
def foo
return 1
end
puts foo + 1
a = foo
Is foo a local variable? A function? I don't know unless I really read the code well. It's clear in small amounts of code, but when your code is large and organized in multiple files and modules, if you start doing stuff like this, things can become very confusing, very quickly. I find the deviation from the mathematical function syntax and metaphor to be harmful.
They were most definitely designed with polar opposite philosophies in mind, but I find TOOWTDI to be more burden than boon. Ruby is, to my knowledge, the only programming language that was explicitly designed with the intention of making the programmer happy rather than the compiler/interpreter, and it shows. So many things become instantly intuitive once you've familiarized yourself with the basics. The selling point of doing it one way is that other programmers will understand your code, but proper documentation alleviates that issue completely, and one should always document code intended to be consumed by other developers.
What's the need to document your code when the language supports self-documenting code? I'm not saying this to say that Python supports this. What I'm saying is that Ruby takes a lot of influence from Perl and Perl is known to be notoriously non-self-documenting. I think this just goes down to philosophy again. I think it's better to have one way to do it and that's just a personal philosophy. It would be a mistake to say that Ruby was a bad language because it didn't sit well with my own personal philosophies. I just don't like Ruby because of it.
Ruby does give one an almost psychotic amount of freedom, but it's not like production code shoots fireworks just because it can. Open classes and being able to attach a closure to practically any method make the language stupid powerful, but of course it must be used wisely, and it's clearly the case that it usually is. I don't hate Python at all; it has a lot going for it, is slightly easier to teach and learn, and is clearly the ideal scripting language for doing any scientific calculation. But having to pass "self" everywhere, single-expression lambdas, and the constant catch-up that it seems to have play with Ruby (Sinatra before Flask, gem before pip) make it less than ideal in my book.
The catch-up you talk about is not really language features but tools that were built with the language. It would be unfair to compare those as language features. They don't really have a place in this discussion, sorry to say
.
I don't disagree that Ruby gives you a lot of freedom. I just find it inconsistent:
Use a local variable before assignment and you get an ArgumentError. Use a global before assingment and you get nil. I can't tell you how many times I've been bitten by this. At this point, it's just really annoying.
>> puts foo
ArgumentError: wrong number of arguments (0 for 1)
from (irb):15:in `foo'
from (irb):15
>> puts $foo
nil
=> nil
Even weirder, assigning an undefined variable to itself before it is defined causes nil. Why doesn't that happen when you try to do the same thing with another undefined local variable?
>> a
NameError: undefined local variable or method `a' for main:Object
from (irb):28
>> a = b
NameError: undefined local variable or method `b' for main:Object
from (irb):29
>> a = a
=> nil
Convert to float using .to_f, you get a best effort. Convert to float using Float cast, you get an exception. Why aren't they in synchronous? Which one is the correct one to use? I'm not sure and I don't think anyone else is either.
>> "03.22".to_f
=> 3.22
>> "03.a22".to_f
=> 3.0
>> Float("03.22")
=> 3.22
>> Float("03.a22")
ArgumentError: invalid value for Float(): "03.a22"
from (irb):24:in `Float'
from (irb):24
Possibly the most annoying thing out of all of this that has recently bitten us at work is dealing with timeout exceptions. Consider the following code:
require 'timeout'
begin
timeout 0.1 do
sleep
end
rescue
puts "got here"
end
When you run this, you'll get
Timeout::Error: execution expired
from /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/timeout.rb:60
from (irb):40
It turns out, Timeout::Error is not a StandardError, so you have to remember to add a rescue for this specific case or your entire program comes crashing down. Apparently this inheritance problem has been fixed in ruby 1.9, but that's the exact kind of issue that I'm talking about. The design was bad to begin with. It's great that it's fixed now, though, but I wonder what the rationale behind doing it like this was. There are more like this:
StopIteration, SystemStackError, LocalJumpError, EOFError, IOError, RegexpError, FloatDomainError, ZeroDivisionError, ThreadError, SystemCallError, NoMemoryError, SecurityError, RuntimeError, NotImplementedError, LoadError, SyntaxError, ScriptError, NoMethodError, NameError, RangeError, IndexError, ArgumentError, TypeError, StandardError, Interrupt, SignalException, fatal, SystemExit
It's almost as if you need to have a general rescue and a SystemError rescue. Why? What's the point of the general rescue if it isn't general?
And then there is this. Here's a visualization of the grammar for ANSI C:
Python:
Ruby:
I think it's pretty clear from these visualizations that Ruby has a much more complicated syntax.
These are just some examples of things that I thought of off the top of my head. After having to work with Ruby at work for the past two years, I can honestly say that I'm not really impressed by the language. I find it inconsistent. I feel like it lays traps for me and waits in the shadows to catch its prey. I don't enjoy working with it. But your mileage may vary.
Hm, this post turned out to be way longer than I expected it to be. Sorry.