|
Few things help an individual more than to place responsibility upon him, and to let him know that you trust him.
-- Booker T. Washington
Why I Love Ruby
When I got up this morning I felt particularly inspired to write an article -- no, an ode -- to the Ruby programming language. I think this is what happens when a programmer finds a language that he or she feels really in tune with. To stretch the analogy, programmers are all tuned to different frequencies, and, if they're lucky, they eventually find that one language with which they really resonate with. Which is probably why there are so many "Language blank is the Best Language Ever!" articles strewn about the internet.
Anyway, I sat down this morning and started making a list of all the reasons I love Ruby. I didn't get too far before I realized that the full list would be too much for one article, so I've decided instead to focus on just one element of my Ruby love affair: Trust.
Ruby trusts programmers.
Open Classes
As an example of that trust, Ruby has open classes. Any developer can open any class and modify it in whatever way he or she feels like. This can mean adding new functionality, redefining existing methods, or even removing functionality (think sandboxes).
Let's say I'm developing board game software. I'll be dealing with board coordinates quite a lot. One natural solution would be to create a Coordinate class with, say, x and y accessor methods:
class Coordinate
attr_reader :x, :y
def initialize( x, y )
@x, @y = x, y
end
def to_s
"#{(97+x).chr}#{y+1}"
end
end
This Coordinate class could be used something like this:
>> c = Coordinate.new( 3, 4 )
=> #<Coordinate:0xb7919440 @y=4, @x=3>
>> puts c
d5
=> nil
>> c.x
=> 3
>> c.y
=> 4
There are two ways we want to use a Coordinate:
- As simple container of x, y pairs
- We also need string representations, like "a1" for (0,0) because these strings are often used in game notations
Don't worry too much about the implementation details, what I really wanted to point out is an alternative:
class Symbol
def x
to_s[0]-97
end
def y
to_s =~ /\w(\d+)$/
$1.to_i-1
end
end
This code allows us to use Ruby's symbols as Coordinate literals, like so:
>> c = :d5
=> :d5
>> puts c
d5
=> nil
>> c.x
=> 3
>> c.y
=> 4
Ruby allows us to open it's Symbol class (if you're not familiar with Ruby's symbols, think of them as immutable strings that instead of being represented within quotation marks, are prefixed with a colon). In this case I've modified Symbol to provide the same interface as Coordinate, effectively overloading Symbol literals so they can be used as Coordinate literals. I, as a programmer, might decide that this trick is worthwhile because it leads to cleaner, more expressive code.
There are, of course, negatives to open classes. What if some library I depend on decides to add an x method to Symbol that behaves differently than mine. Such conflicts would no doubt lead to bugs (and potentially hard to find ones at that). Obviously, redefining methods or removing methods could lead to other compatibility problems. Some programmers refer to he process of opening and modifying classes as monkey patching, which should be a fairly strong hint that some programmers disapprove of the technique.
So, when should you take advantage of open classes? Really it's up to you. Maybe you're a more conservative programmer and only see benefit in making temporary bug fixes to third party libraries. Or, maybe you're a huge fan of Ruby Facets. Either way, Ruby allows you to draw your own line in the sand.
Not every programming language will trust you to modify its core classes. I suspect more OO languages have closed classes than open classes. Some, like Java, take it a step further: The Java final keyword is often used to prevent programmers from even subclassing its core classes.
Duck Typing
When defining a method, it's considered idiomatic in Ruby not to check the class of parameters. For example, the following is not encouraged:
def neighbors( c )
raise "Not a Coordinate" unless c.kind_of?( Coordinate )
[Coordinate.new( c.x + 1, c.y ),
Coordinate.new( c.x - 1, c.y ),
Coordinate.new( c.x, c.y + 1 ),
Coordinate.new( c.x, c.y - 1 )]
end
With the above code, neighbors won't work when given one of the cool symbols we created earlier. Technically, all neighbors needs to work is an object that responds to x and y method calls. So we could rewrite the above like so:
def neighbors( c )
unless c.respond_to?( :x ) && c.respond_to?( :y )
raise "Not Coordinate-like"
end
[Coordinate.new( c.x + 1, c.y ),
Coordinate.new( c.x - 1, c.y ),
Coordinate.new( c.x, c.y + 1 ),
Coordinate.new( c.x, c.y - 1 )]
end
Of course, we could just chill out and write it like this:
def neighbors( c )
[Coordinate.new( c.x + 1, c.y ),
Coordinate.new( c.x - 1, c.y ),
Coordinate.new( c.x, c.y + 1 ),
Coordinate.new( c.x, c.y - 1 )]
end
In which case, garbage-in would cause an error like this:
>> neighbors( {} )
NoMethodError: undefined method `x' for {}:Hash
from (irb):26:in `neighbors'
from (irb):35
I gave neighbors an object that doesn't provide a Coordinate like interface (an empty Hash) and got a reasonable error. In fact, I'd probably prefer the default error message above to either of the error messages in the previous examples.
By relaxing, and not worrying about type checking parameters, I'm trusting future programmers to pass valid coordinate-like objects. This is duck typing: if something walks like a duck, and quacks like a duck, why should I care whether or not it really is a duck?
Ruby's dynamic typing and duck typing philosophy allow programmer creativity and inventiveness. For example, creating proxies and mock objects in a duck typing environment is a breeze.
None of this would be possible without trust.
Happiness
Ruby's creator, Yukihiro Matsumoto (Matz), has said many times that one of the biggest objectives of the Ruby programming language is programmer happiness.
I think a lot of happiness is achieved through Ruby's expressive syntax. For many, Ruby code is easy on the eyes, and there's a certain joy in writing elegant code.
But for me, happiness comes from freedom. There is nothing more frustrating than working around restrictions inherit in your programming language. Sometimes the restrictions are tolerable because they're a part of a trade off. Erlang, for example, places restrictions on its programmers, but those are for the sake of providing a high degree of parallelization and fault tolerance.
But some languages, like Java, are restrictive due to a lack of trust. Sometimes I wonder if the Java motto isn't: Remove all the sharp tools from the toolbox before some programmer pokes his eye out.
I prefer my toolbox fully stocked. I prefer a relationship built on trust. And, that's why I love Ruby.
|
Nicolás Sanguinetti says,