6 Weeks with Ruby
November 11, 2007
When I started working for Slicehost I went from working primarily on Django projects to Ruby on Rails. Previously I had little experience with Ruby, though the similarities between Ruby and Python have made for a relatively smooth transition. Many of the differences between the two languages are the same between any other set of languages, which is commonly just a slight paradigm shift. However, Ruby has some unique peculiarities.
Symbols
The first thing I noticed about Ruby (and Rails) is the abundant use of symbols. For a programmer not used to Ruby, it’s hard to explain what a symbols and why they might be used. The Ruby website explains symbols thusly:
A symbol is all about who it is, not what it is.
Why’s Poignant Guide says:
Symbols are lightweight strings. Usually, symbols are used in situations where you need a string but you won’t be printing it to the screen.
A good way to think about the importance of symbols is the use of a hash (aka dictionary for us Pythonistas). In a hash we set up a series of key/value pairs to be accessed via the keys:
jared = {'awesome' => true, 'funny' => 'not really'}
The attributes themselves are not the important part, but the values of those attributes. So instead we use symbols as keys:
jared = {:awesome => true, :funny => 'not really'}
In this way symbols become the identifiers in a hash. This is a convention I would love to see in other languages, notably Javascript and Python.
unless and inline evaluation
A neat thing about Ruby is how much it is engineered to read like natural language. One example is the implementation of unless. This is basically the reverse of if.
a = 1
puts 'a is not 1' unless a == 1 # will not print
puts 'a is 1' if a == 1 # will print
This example shows how effective and readable the unless statement is, and clearly shows the utility of inline evaluation. Most languages allow inline evaluation with an if/else statement, but not usually on the right side.
Locality of blocks
Blocks of code are snippets surrounded by curly braces, or do and end. Iterating over an Enumerable class uses a block, yet inside a block variables are local. For example we have Python:
for a in [1,2]:
b = a
# Outside the block we can access b
print b # Prints '2'
In Ruby, b is not accessible outside the block:
[1,2].each do |a|
b = a
end
puts b # Raises NameError
But you can use previously assigned variables inside a block:
b = 10
[1,2].each do |a|
b = a
end
puts b # Prints '2'
Implicit returns
In most languages, when a function doesn’t explicity return a value, the return value is a null value. Ruby, however, always returns a value. The last line, block, or statement is what is returned from a function or method call. Consider this:
def my_function
a = 10
a
end
Here we’ve returned the value of a which is 10. Now consider this:
def my_function
a = 10
end
Coming from another language you might think this will return the value of the assignment, which would be true if it succeeds. In Ruby assignments return values as well. So in this case you’re returning the value of the assignment of a, which is 10.
At work I came across an interesting gotcha that can prove hard to find. We had a function similar to this:
def can_has_cheezburger?
my_array = [1,2,3]
my_array.each do |a|
return true if a == 5
end
end
The gotcha here is that the function will always evaluate to true since if the function doesn’t return true inside the array loop, it will return the array. (In Ruby, any value besides false and nil will evaluate to true — even 0 or an empty array.)
And on and on…
Some of the problems I have had with Ruby are due mostly to expected behavior I learned from commonalities in other languages. Ruby acts differently than others in various ways, which I believe is due to its different approach to object-oriented programming.
I still use Python for my tasks at home but that is because I am more familiar with it. Though the more exposure to Ruby I have, the more I find I like it. And part of liking Ruby is appreciating its unique model.
Those who debate the relative merits of their preferred system over the competitors alleged faults tend to do more harm than good. It pits developers against one another when in reality we can all learn something from one another. We all hate Microsoft for spreading anti-FOSS FUD, but in the end aren’t we doing the same when we decry competitive technologies?
Add your comment
No HTML; Only URLs and line breaks are converted.
Comments
I agree. python has improved many of its lackings from perl with the addition of easy-install but with all the extra work, I wonder if it was easyer in ruby because it was an idea from the creantion of the language.
Posted by Nehemiah
Hey Jared, nice to hear of a python user's travels in strange new worlds :-)
I'm also quite a fan of symbols (I call them attributes, but I think I'm the only one)... they would definitely be useful in Python! Doesn't javascript have something similar, with their objects?
I'm not a big fan of the "unless" syntax... I find it quite inefficient, to be honest (natural to the english language perhaps, but the english language is not the only language, nor the most efficient :-) ). My beef with it in programming languages is because I see a program as a series of instructions... and lets face it, if you're driving a car and someone tells you "turn left at the exit UNLESS the exit isn't exit-35!" you're gonna want to change navigator reaaal fast :-)
I suspect locality of blocks is something the python programmers never bothered with due to the fact that closures aren't an explicit part of the python language... I might be wrong, but I've seen some people complain about creating lambdas in a loop where each lambda uses an incrementing var from the loop and the lambda doesn't "save" the variable at the state it was when the lambda is created (ie. like ruby does when you create a closure)... also, it might be an efficiency thing :-)
Implicit returns : to quote a wise man "Explicit is better than implicit."
cheers!
ps. congrats on your gig at slicehost... i'm quite a big fan of theirs (have a slice there).
Posted by MarcC
Nice writeup. I went from rails to django, not because of ruby because ruby is nice, but more because i found out django to do webdevelopment for me better than rails
Posted by Martin
All of the peculiarities mentioned above come from Lisp. In that aspect those are, indeed, less pecularities, just missing features from most of the languages out there.
Posted by slink
You can also iterate through an array without using blocks and it will look (minus an ':') and work like it was Python:
for a in [1,2]
b = a
end
print b
Posted by nons
"It's just much easier to sustain a consistent way to do things, which is very important in a collaborative project."
For this you implement conventions, and people will stick to them. If they dont, you wont merge their code. Period.
This is as easy as it gets, and by the way you can write ugly python code just as well.
When I work on ruby projects i try to convince everyone of avoiding using @@class_vars and lambdas. Unfortunately this is not always possible because someone else wants to use them, and if he leads the project, i just follow along.
But personally I always avoid these things whenever possibl (unless it would i.e. make the code a LOT shorter, which is never the case for either @@ or lambdas)
"Ruby's variable binding scheme is pretty confusing"
I think you over-estimate the "confusing" aspect. I am using ruby since 4 years, and aside from the initial learning curve I never had a problem with local vars.
"Symbols: Strings are mutable in Ruby, so in order for you to have efficient hash maps/dicts with strings as keys you need this 'symbol' concept."
This, as you put it, is wrong. Symbols are _ALREADY_ in the language, and their advantage is, because they are immutable and always the same, they are FASTER.
Look at the C implementation of Ruby!
Symbols are used in the language EXACTLY because they are faster AND unique.
You can just as easily use string 'keys' for your hash, no big deal at all. But because ruby is not so fast... ;-) and because a :key is quite easy to type as well AND because in most cases you do not NEED a string as a key, symbols are used a lot.
By the way last but not least, I am a bit sad you did not point at the best part in Ruby which beats everything else:
The OOP
Exactly the OOP philosophy is what sold me on Ruby (and I think Python is a good language, a lot better than perl, and it has some areas where it is better than Ruby too, such as the Unicode part, or bindings to bigger projects like Ogre3d)
Really people, it is the OOP model which is the real beauty. It is like a very practical, like a short smalltalk variant, which never stopped evolving into being both beautiful and practical but without the unneeded Squeak variant or useless "one dir per class, one file per method" crap...
Posted by she
@MarcC re: 'unless'
I think it's more like:
Turn left at exit 35 unless it looks really busy.
Or similar. It's definitely not applicable in all situations.
It mostly just helps with things like
if not error
..do_something()
end
turning into
unless error
..do_something()
end
Posted by ryan
Bjorn doesn't "really see what symbols add"...
IMHO, the important thing about symbols is a conceptual thing, related to their name in Lisp: "Atoms". That is, they are (in Ruby physics) a basic elementary particle, and they aren't made up of smaller things. Strings, even immutable ones, are a more complex concept -- they are made up of characters, and there's always the thought about whether you're talking about the string or one or some of its characters, and the question of mutability because you've also used strings in languages other than Python.
After years of using Python, I've worked for the past 2 and 1/2 years in Ruby, writing a compiler. I do end up using Symbols fairly often -- as cheap, super-lightweight extensible enums, as hash keys, as field descriptors for hardware config fields.
As I'm about to move back to working in Python, I'll miss Symbols a bit.
Posted by Doug L.
marcr,
'Unless' should only be used where you would normally use 'if not.' Obviously you wouldn't want a navigator who says "turn left at the exit if not the exit isn't exit-35!"
Posted by Josh
quoted from 'she':
"(Python) has some areas where it is better than Ruby too, such ... bindings to bigger projects like Ogre3d)"
Bad example.
check out http://ogrerb.rubyforge.org - It will rock your face off.
Posted by Mikkel Garcia
def can_has_cheezburger?
my_array = [1,2,3]
my_array.each do |a|
return true if a == 5
end
end
Not meaning to nitpick and I know you used this for sake of demonstration, but this code would probably more conventionally be written as:
def can_has_cheezburger?
my_array = [1,2,3]
my_array.include? 5
end
I think most Ruby programmers tend to discard whatever Enumerable#each returns (which, as you say, may seem counter-intuitive at first). The use of Enumerable#include? is an example of how implicit returns can lead a programmer to see his code as a composition of other functions, rather than just a collection of subroutines.
Good article -- it takes a fair amount of grace to write a balanced Ruby/Python comparison, and I think you pulled it off.
Posted by AndrewO
One way vs many ways of doing things: I agree conventions are of course also necessary, but a language design that enables many ways of doing the same thing of course makes it harder to maintain conventions to avoid inconsistencies.
Symbols vs strings: The point is that Python strings play the role of Ruby symbols. The benefit: strings are fast and can be used as keys in dictionaries efficiently, and there's no need to introduce another concept into the language. The drawback: some people like to have a different name for this use of strings. There really isn't any benefit to using symbols except that some people think it fits better conceptually.
Object-orientedness of Ruby: I guess that by this you mean that Ruby has a lot of member methods on its basic types, whereas Python chose to expose those methods as globals? Besides this, Python is just as dynamic and OO as Ruby.
Overall, Ruby and Python are very similar; dynamic, high-level, with a focus on readability. The main battle these days is really more on the dynamic vs statically typed languages, and both Ruby and Python are on the same side in this battle.
Python is a bit more conservative with respect to how much DSL capabilities to introduce into the language (because of fears that it'll impact readability/understandability), whereas Ruby is much more aggressive. Personally I think Python makes for more readable code (I hate the Pascal-isms of begin/end or {}) but I understand people that are excited about having the power of DSLs and purist Smalltalk-like OO syntax.
Posted by Bjorn
Nice writeup!
Why is "implicit return" considered a feature? Isn't it better to specify what you are returning in the method declaration, and explicitly return the value you want to pass?
I can see if conditions where you might end up "returning" a value you didn't intend to. I don't know why this is listed as a feature, it seems more like a bug.
Posted by Augusto
Augustu:
I believe it's in line with Ruby's philosophy of keeping the code very clean. You can always include a return statement if you wish. If you are coming to Ruby from another language I guess it can seem a bit odd at first but you get used to it really quickly.
I code in both Python and Ruby, love them both for various reasons. Sure is a luxury to be able to choose between them!
Posted by Stefan
"In Ruby assignments return values as well"
That's true of every language I've tried, including Python and C++. I'm not aware of any language in which you could write "b=a=10" and expect b to equal "true".
The only unique part about it with Ruby is the implicit return of the value -- in C++ you could write "return a=10;" and you'd get 10 returned.
Posted by Pete
A good description of Symbols can be found here. http://glu.ttono.us/articles/2005/08/...
The first reason to use Symbols of course is that it uses a lot less memory that Strings. Another reason is that I find that it kinda makes things clearer.
And MarcC. You can still return a value explicitly. Take for example:
def do_something()
return 666
a = 5
end
>> do_something
=> 666
So you can (and one often does) return explicitly. But in Ruby everything has to return an object and Matz chose to simply give people a choice. And I kinda like choices.
Posted by JonGretar