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