Ruby and The Principle of Unwelcome Surprise
ceaude.twoticketsplease.de
ceaude.twoticketsplease.de
It's trendy to pick holes at Ruby in various, inaccurate ways lately, but a quote from Matz about that POLS business:
Everyone has an individual background. Someone may come from Python, someone else may come from Perl, and they may be surprised by different aspects of the language. Then they come up to me and say, "I was surprised by this feature of the language, so therefore Ruby violates the principle of least surprise." Wait. Wait. The principle of least surprise is not for you only. The principle of least surprise means principle of least my surprise. And it means the principle of least surprise after you learn Ruby very well. For example, I was a C++ programmer before I started designing Ruby. I programmed in C++ exclusively for two or three years. And after two years of C++ programming, it still surprised me.
You can pick up almost any programming language, not learn it properly, and then pick holes in its various inconsistencies or flaws (my own is over Python's ambiguities over methods vs functions for various things - but I know I'm not a Python developer!) That doesn't make it interesting or insightful. Every language has dark corners and Matz has been more than forthcoming about Ruby's.
This article has numerous curious assumptions and sloppy practices. Local variables called the same as method names? And very few Rubyists, in my experience, would be confused by the key example in the memoization section. Who really thinks || checks for whether a variable is bound rather than doing a logical or? How is it a "what's worse" that instance_eval changes self to instance_eval's receiver? That's its entire reason for existing! The second example under "Local Variables Strike Yet Again" is flat out wrong, even by the author's own admission.. Ruby 1.9 has been the production release for almost three years!
Think the only related thing I've ever hit and gone WTF at is `foo = foo` sets foo to nil. But afaik that's due to the implementation of MRI/YARV, not all ruby runtimes.
So yeah, as a professional rubyist, reading that post was mostly just me going "WTF, why would you ever think or do that". As Peter says, take it with a pinch of salt.
I'm reminded of a programmer I read about who refused to learn any language's operator precedence. Instead, he always grouped expressions with parentheses to make sure they were evaluated in the order he desired. (I don't know that I'd do that myself, but he had a point...)
If you are a C programmer maybe you can learn to live with those and manually "resolve them".
But if you dabble in functional languages, e.g Haskell, that's something you come to expect, if not demand of your language.
A programmer should NOT do what a computer can.
My case was against Ruby back when Rails was hot. Ditto with JavaScript nowadays.
Ruby 1.9 has been out for production for almost 3 years? Cool, how come it's slower than 1.8.7? (not trying to offend Ruby but I use Rails 3.1 with Ruby 1.9, running unit-tests are dog-gone slow).
It's not. File loading in 1.9.2 is notoriously slow and you're probably encountering a common case where mid to large scale Rails 3 apps are loading so many files at runtime that you're hitting it. Everything else is bright and breezy. The story behind much of this: http://www.rubyinside.com/ruby-1-9-3-faster-loading-times-re... - Most people are experiencing significant speedups with 1.9.3 so far but it hasn't had its final release yet.
That aside, speed isn't everything. Python 3 was (is?) slower than Python 2 for quite some time, despite being in production.
Actually every time somebody uses the term "FUD" I have to take THEIR comment with a huge sack of salt.
Can't you just state your counter-claims without resorting to accusing the other guy of a conscious attempt at misguiding people?
Also, "FUD"? Really? Someone posting on some Ruby shortcomings (or anything) is spreading "fear", "uncertainty" and "doubt"? Because you just had to use a relic term of the 90's desktop wars against Microsoft, right?
I read the post, and haven't felt any particular fear.
Matter of fact, I was rather enlightened by some of the examples.
Also I take offense to the silly line of argument here:
> "You can pick up almost any programming language, not learn it properly, and then pick holes in its various inconsistencies or flaws".
Really? One picks holes "in its various inconsistencies or flaws" if he has not "learned [the language] properly"?
What sense does that make? A language either has "various inconsistencies or flaws" or it has not.
If it has --and you don't argue that Ruby hasn't--, one has EVERY F*N right to "pick holes" at them. And this has nothing to do with "not learning the language properly".
If the language has "inconsistencies or flaws" what does "learning it properly" mean? To learn to work around them?
That's a bizarre definition of "properly".
I've found that Ruby is a very right-brain language in the sense that when you analyze it at a low level, things don't seem to make sense. But at a high level, it often does exactly what you'd want. Variable hoisting in 1.9 is the perfect example of this.
OP claims to have been a "Rubyist", but it sounds like he's still fighting the system and hasn't embraced it yet. It's like when people who are new to Lisp complain about the parentheses or prefix notation. If you just go with it, down the road you may find that these are some of its best features.
http://www.coffeepowered.net/2009/03/02/syntactic-sugar-will...
Here's a cleaner way to visualize it:
def foo
if false
bar = "baz"
end
lambda { bin = "bang" }.call
return local_variables, bar
end
def foo_two
if false
bar = "baz"
end
lambda { bar = "bonk" }.call
return local_variables, bar
end
puts foo.inspect
puts foo_two.inspect
=> [["bar"], nil]
[["bar"], "bonk"]
Obviously the code that initializes bar never gets run, but Ruby initializes a local variable "bar" anyway, since there is a left-hand assignment to it present in the method. The lamba defines a new scope, so its local variable assignments don't leak to its containing scope, but in that scope, unless there was a variable named bin initialized in the outer scope already, which is the case for the lambda in foo_two, which leaks the bar reference to its containing scope since bar was defined in the outer scope at the time of the lamba's definition.I saw Neal Ford's "Abstraction Distractions" recently and a lot of things resounded with me, but one that I liked was a general rule that abstractions should solve 80% of your cases easily, and get the hell out of the way for the other 20% and let you get further down the abstraction tree to solve them yourself. I believe this does.
Others look at static typing w/inference and/or better module systems (O'caml, haskell, scala, F# if you already own Vis. Studio). With these, whitespace/precedence/associativity parsing issues can be amplified or mitigated, depending on whose code you're reading, but once you absorb the language's Gestalt or Sprachgefühl they shd go towards mitigation.
(Go and erlang are getting mindshare also)
This is mainly caused by the false intuition that
@foo ||= bar
actually expands to
@foo = @foo || bar
which, to many a surprise, is not the case.
Hardly a "false intuition", since that is how this syntax works in other languages, e.g., perl[1].http://davybrion.com/blog/2010/08/compilerinterpreter-warnin...
Perl's "my" really patches over a dangerous default. Perl gives you the global by default. Ruby gives you a local by default. I'd much rather have my defaults be sane than be forced to make declarations to fix things up. Which is why Ruby became my primary language after 8 years of Perl..
I'm pretty sure autarch knows that.
I come from the Python world (and have done quite a bit of Ruby as well as a few other languages) and have come to dislike all implicit scoping, it is less readable and always ends up leading to annoying (and hard to track) bugs. This is magnified in Python due to scoping being historically wonky (there used to be no lexical scoping at all, only function-local and module-global) and leading to `global` and (in Python 3) `nonlocal` (yay two crappy keywords and statements instead of having a single clear one)
Whatever the case, these will trip people up and really provide little to no gain, explicit lexical scoping is clearer and leads to less bugs, both from shadowing an existing variable and from hooking into (and overwriting) the same. It's also pretty easy to statically check, in a statically (lexically) scoped language.
Interestingly, that (along with first-class blocks, keyword messages and the cascading operator) is one of the very nice things Ruby threw away from its (limited) Smalltalk ancestry.
> Ruby gives you the safe, local
and often wrong
> thing by default.
It's enough to drive a man back to Scheme. Why do all of these sh++ languages get so much right and so much wrong? My personal preference from the list is Python, but it's full of irritants as well.
$ pychecker a.py
Processing module a (a.py)...
Warnings...
a.py:3: Local variable (somevar) not used
a.py:4: Local variable (someothervar) shadows global defined on line 1
a.py:5: Local variable (someothervar) shadows global defined on line 1
a.py:5: No global (some_func) found
not every problem with scoping can be caught that way, but hey... can't have everything.Huh. Everywhere? Like, even in inner scopes where you mention the variable? If not, what exactly does "everywhere" mean here?
The nice thing about "strict" is that it forces you to be explicit about the scopes -- the compiler can't give you explicity with implicit behavior.
my_variable = 10
# a few lines later...
my_varable = 15
Some kind of "var" keyword makes the problem disappear and makes the programmer's intentions clearer, e.g., when creating temporary variables inside the scope of a conditional or a loop.In the case of Perl, strict mode also makes it clearer when the programmer wants a lexically ("my") or dynamically ("local") scoped variable, a feature woefully missing in both Ruby and Python.