Emancipate yourself from mental slavery, none but ourselves can free our minds. --Bob Marley, Redemption Song
There are a number of reasons to love Ruby and I will share some of them here. Ruby is influenced by some of the greatest languages ever invented: Perl, probably the most pragmatic language in the world, hell, even God used it :)
Ruby is also influenced by Smalltalk and Lisp. Both these languages are still living, vibrant languages despite their respectable age, 40 and 60 years respectively.
When Matz chose parts from the above programming languages his primary design goal was Programmer Happiness. He succeeded.
Here are some features that I love about the language.
Parallel Assignment
You can assign many values on the same line by separating them by commas. They are assigned in parallel. No need for temporary variables.
# Swap a and b, without a temporary variable a, b = b, a
Multiple Return Values
If you want to return more than one value in Ruby all you need to do is to wrap it in an array. Then you can get the values by using commas. Also note, that return is not required in Ruby, the last evaluated expression is returned.
def div(a, b) [a/b, a%b] end a, b = div(5, 2) # a = 2, b = 1
String Interpolation
Double-quoted strings can be interpolated.
kid = 'Kid' "Hello #{kid} ##{1+3}" # => Hello Kid #4
Here documents
You have seen them in other scripting languages and they are as good as they look. No need for String concatenation or embedded newlines (\n).
sql = <<-SQL SELECT allocations.project_id as project_id, year, week, allocations.id as allocation_id FROM weeks LEFT OUTER JOIN allocations ON allocations.start_date < weeks.end_date AND allocations.end_date > weeks.start_date AND allocations.project_id = ? WHERE weeks.start_date >= ? AND weeks.end_date <= ? SQL
Higher Order Functions
Higher order functions are easily expressed with Ruby blocks.
# Map converts an array of values to a new array of values ["a", "b", "c"].map { |item| item.upcase } # => ["A", "B", "C"] # Zip zips arrays together [1, 2, 3].zip([10, 20, 30]) # => [[1, 10], [2, 20], [3, 30]] # Zip with a function applies the function to the results [1, 2, 3].zip([10, 20, 30]) {|arr| arr[0] + arr[1]} # => [11, 22, 33]
Simple Command Line Access
The command line is always readily available in Ruby
# Backticks invokes the command and returns the result as a string `ls` # => "Scripts\nStylesheets\nbin\nget_destinations.scpt\nlib\nnibs\n" `ls`.split # => ["Scripts", "Stylesheets", "bin", "get_destinations.scpt", "lib", "nibs"] # System invokes the command, prints the result to stdout, and return a boolean system('ls -l') total 64 drwxr-xr-x 3 andersjanmyr admin 102 Aug 29 2009 Scripts drwxr-xr-x 3 andersjanmyr admin 102 Aug 29 2009 Stylesheets drwxr-xr-x 5 andersjanmyr admin 170 Aug 29 2009 bin -rw-r--r-- 1 andersjanmyr admin 12312 Aug 29 2009 get_destinations.scpt drwxr-xr-x 3 andersjanmyr admin 102 Aug 29 2009 lib drwxr-xr-x 3 andersjanmyr admin 102 Aug 29 2009 nibs # => true system('ls tapir') ls: tapir: No such file or directory # => false
Open Classes
Open classes mean that it is possible to add new methods to already existing classes. This is very powerful.
class String def vowels self.scan(/[aeiouy]/i) end end "A tapir is beautiful".vowels # => ["A", "a", "i", "i", "e", "a", "u", "i", "u"]
There are two common reactions to this.
- Wow! This is really cool, this will allow me to put everything in the right place.
- What! That is too dangerous, our programmers will create an unmaintainable system.
I'm in the first camp!
Class Inheritance
In Ruby the classes are objects and follow the inheritance rules of objects. No statics are needed.
class Mammal # This is a class method, that returns all mammals of this kind def self.all mammals.select do |m| m.kind_of? self end end # Another class method that gives access to the class varibable def self.mammals # @@ is a class variable prefix @@all_mammals ||= [] end end class Tapir < Mammal end # Add some mammals and a tapir Mammal.mammals << Mammal.new << Mammal.new << Tapir.new Tapir.all # => [#<Tapir:0x000001010fc1a0>] Mammal.all # => [#<Mammal:0x000001010fc218>, #<Mammal:0x000001010fc1c8>, #<Tapir:0x000001010fc1a0>]
Meta Programming
When the classes are parsed, Ruby is already running and since it is possible to create methods and classes on the fly you can do some very powerful meta-programming with Ruby. Here is a simple example.
class Tapir [:sniff, :eat].each do |method_name| send :define_method, method_name do |thing| "I'm #{method_name}ing #{thing}!" end end end t = Tapir.new t.eat 'bananas' I'm eating bananas! t.sniff 'glue' I'm sniffing glue!
This type of meta-programming is what enables the simplicity of Rails' has_many
, and belongs_to
methods for declaring relationships.
Method Missing
The last feature of Ruby that I want to share is method_missing
. method_missing
is a feature that Ruby inherited from Smalltalk. If an object is invoked with a method that is not defined by its class or ancestors, method_missing(method, args)
is called instead. By implementing this method you can effectively create all sorts of cool stuff such as proxies, prototypes, etc.
# Simple implementation of a Javascript-like structure without inheritance class Prototype def props @props ||= {} end def method_missing(method, *args) m = method.to_s.sub('=', '') # Strip the trailing '=' to allow setters if args.empty? props[m] else props[m] = args[0] end end end prot = Prototype.new prot.age # => nil prot.age 14 # => 14 prot.age # => 14 prot.age= 16 # => 16 prot.age # => 16
Inside method_missing
it is of course also possible to define new methods and classes dynamically, but I'll leave that for another day.
Ruby is a supreme dynamic language that lets you do almost anything. Try it!
When you can do anything, you have to become more responsible. You own your fate. And, that, I think, is a situation which promotes responsibility. -- Michael Feathers, author of Working Effectively with Legacy Code
No comments:
Post a Comment