Wednesday, July 12, 2006

Ruby sugar you can't find in C#

Coming as a C# programmer to learn Ruby, as I journey from a compiling language to a dynamically typed language, of course there are some concepts in Ruby that are easier (or harder) for me to grasp than others. I want to write out loud some of these interesting bits and pieces for others who are also interested in trying out Ruby.

String vs. Symbol
In Ruby a string can be single-quoted or double-quoted (eg. "foo"). A symbol is a variable name prefixed with a colon (eg. :foo). Symbols somtimes are used pretty much synonymously with strings. The reason they exist is that by using symbols, in memory they only exist as a single copy per symbol no matter how many times you refer to them in your code. String however will create a new copy of the string in memory every time you refer to them, even though they are exactly the same. This in C# is equivalent to string interning, and is the difference between using between a StringBuilder and regular string concatenation. So don't be baffled when you see them next time.

Methods vs. Messages
In Ruby, everything is an object, including a number and a string. Ruby programmers like to think that when you are calling a method on an object, you are sending a message to that object in hope of it doing something for you. One of the cool features in Ruby is that one can call methods that are not defined at programming time but define and call them at runtime. For example:

class Foo
def programming_time_method
puts "programming time method"
end

def define_more_methods
self.class.class_eval do
define_method(:new_born_method) { puts "i was born!" }
end
end
end

Now if you do:
f = Foo.new
f.programming_time_method
puts f.respond_to?(:new_born_method) #Does f have method by that name?
f.define_more_methods
puts f.respond_to?(:new_born_method) #Does f have method by that name, now?
f.new_born_method

then you get results:

>ruby test.rb
programming time method
false
true
i was born!

One can do much more fancier stuff with Ruby too. Read on.


Class Methods vs. Static Methods
In C# most developers I worked with frowned upon static methods. They are flat-out evil. You cannot effectively mock method calls of them, and that they encourage non-OO style procedural programming. Pretty much all of the arguments that they are good for something are trumped by the hard-to-test reason. Introducing class methods in Ruby:

class Customer
def self.class_method
puts "class method getting called"
end
end

Customer.class_method
>ruby test.rb
class method getting called


At first glance they act just like static methods, you do not need an instance but just the class type and you can start making calls on them. However, they are no longer un-mockable. The unit testing framework that comes with Ruby also contains a mock framework. It can mock class methods. All of a sudden class methods, or should I say methods that associate to a class, deserves another look. In fact, Ruby on Rails uses class methods extensively for some of its magical operations. For example, if my Customer class inherits from Model, and has an id and a name in the database, I automatically get these magic methods for free - yes, free!

Customer.find_all                # returns the list of all Customer instances from database
Customer.find_by_id(2) # returns the Customer instance that contains id=2 in database
Customer.find_by_name('STEPHEN') # returns the list of all Customer instances whose name is STEPHEN


When you add a column to your table in the database, you get a free find method without coding. That's Rails.

Method-missing
Any message (aka method) you send (aka call) to an object, if that object does not yet have that method defined, it will go into its method_missing() method, which you can override. From that point, one can be fairly creative given the dynamic nature of what Ruby allows a programmer to do. In fact, method_missing() plays a pretty important role in constructing a DSL (Domain Specific Language).

class Customer
def method_missing(method_symbol, *args)
puts method_symbol.to_s
return self
end
end

customer = Customer.new
customer.pays.me.five.dollars

>ruby test.rb
pays
me
five
dollars

More Ruby sugar will follow. Stay tuned.

4 comments:

Dave Hoover said...

Thanks Stephen. Good post.

Anonymous said...

This is all available in Smalltalk too.

I miss Smalltalk.

Anonymous said...

Interesting post.

Something different though, what about using C# and Ruby together? We have a large system built in C# and would love to start using Ruby for web apps, but we are not going to rewrite all the logic we have sitting in C#.

What's the best way to achieve this?
Guy

Dans said...

happy new year in your hand very good health