Three Ruby related posts in a row! Yes, I’m currently enamored with this colorful little language.
If you know people that know (or you yourself know) languages like Smalltalk and Lisp, you’ll often hear them curse when they use other languages. Something like “Damn, I could do this so much easier in Smalltalk. To do x would only take me 2 lines of code!”. This is the best reason NOT to learn Ruby. Every time you program in other languages you’ll think of all the little tricks you wish you could use from Ruby. I figured I’d share the pain (or at least what I think are some of the cool features).
Closures and Blocks
Closures and Blocks are most used in Ruby in dealing with Arrays, Hashes and other like structures. They can be used in many places, but they are widely used in library routines for Enumerable types, so you get a lot of built in examples of that usage.
Does an Array contain a specific item?:
1 | me = people.find { |p| p.first_name == 'Geoff' } |
Find all of elements matching the condition:
1 | myfamily = people.select { |p| p.last_name == 'Lane' } |
Create a new array with the results of running the block on each element (uniq then removes duplicates):
1 | all_families = people.collect { |p| p. last_name }.uniq |
All of that stuff without writing a single Iterator or for loop!
Blocks can be stored as a variable to call later and can be passed as an argument to a method. This can give you built-in support for light-weight version of patterns like the Strategy Pattern or the Observer Pattern.
An Observer for example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | class Subject def initialize @blocks = [] @state = nil end def attach(&block) @blocks < < block end def state=(v) @state = v notify end private def notify @blocks.each { |b| b.call(@state) } end end class Observer def called puts "Observer called." end end class Observer2 def called(state) puts "Observer2 was called with '#{state}'." end end o = Observer.new o2 = Observer2.new s = Subject.new s.attach { o.called } s.attach { |s| o2.called(s) } s.state = 'Hi' |
Gives us:
Observer called. Observer2 was called with 'Hi'.
A Strategy Example:
1 2 3 4 5 6 7 8 9 10 11 | class Mathify def initialize(x, y, &block) @x = x @y = y @block = block end def call @block.call(@x, @y) end end |
1 2 3 4 5 6 | add = proc {|a, b| a + b} mult = proc {|a, b| a * b} m = Mathify.new(2, 3, &add) n = Mathify.new(2, 3, &mult) puts m.call puts n.call |
Metaprogramming
Metaprogramming as the name implies is ‘programming your programming’. Using the dynamic nature of a language like Ruby (or Lisp where much of this originated), you can programmatically add functionality to your classes or instances at runtime.
Built-in examples:
1 2 3 4 5 | class Person attr_accessor :first_name, :last_name attr_reader :person_id alias :last_name, :family_name end |
1 | attr_accessor |
and
1 | attr_reader |
dynamically create accessors for your class.
1 | alias |
creates another name that can be used to access the same attribute.
Ruby has a lot of built-in support for this kind of functionality, but it also allows you to add your own as well:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | class Module def log_call(method, *args) old_method = instance_method(method) define_method(method) do |*args| puts "Calling '#{method}'." old_method.bind(self).call(*args) end end end class MyClass def call_me puts "I've been called." end def call_me_too puts "I've been called too." end log_call :call_me_too end mc = MyClass.new mc.call_me mc.call_me_too |
Prints:
I've been called. Calling 'call_me_too'. I've been called too.
As you see, I’ve ‘decorated’ one of the method calls with some new functionality. Basically, I’m dynamically redefining the method to log the call prior to passing along the call to the original implementation. You could intercept calls to set a ‘dirty’ flag to know that an instance had been changed so that you could do automatic caching.
Open Classes
The other thing that this code example shows is that Classes in Ruby are never closed. You can at any time, open a Class add new methods, redefine methods, remove methods, anything you want. This is really great for Object Oriented programming because you can choose where a method resides based on where it is most appropriate.
1 2 3 4 5 6 7 8 | class Array def first_half slice(0, size/2) end end a = [1, 2, 3, 4, 5, 6] puts a.to_s puts a.first_half.to_s |
Prints:
123456 123
So, if you have special needs to deal with built-in Classes or even just Classes from libraries that you are using, you can put the methods with the Classes where they rightly belong.
Mixin Modules
Ruby is a Single-Inheritance language and it does not have Interfaces. Ruby believes in Duck Typing, if it has the right properties and methods, use it. What it has instead of multiple-inheritance or interfaces is Modules.
When you
1 | include |
a Module into a Class, it ‘mixes in’ its properties and methods into the Class. This is widely used in the core Ruby classes.
1 | Enumerable |
is used to implement a whole suite of features for interacting with lists of objects. Array and Hash both include
1 | Enumerable |
to give them the implementation and interface of this code. You can use
1 | Enumerable |
in your own code of course, and as with all the other features I’ve talked about, you can create your own as well.
1 2 3 4 5 6 7 8 9 10 11 12 | module Example def contrived puts "This is contrived." end end class Silly include Example end s = Silly.new s.contrived |
Prints:
This is contrived.
Conclusion
Those are some of my favorite features of Ruby, but you really should ignore them. If not, when you have to programming in Java or .NET you’ll be one of those people saying “Damn, I could do this so much easier in Ruby. To do x would only take me 2 lines of code!”
Pingback: Zorched.Net » Blog Archive » Ruby 1.8 (today) vs C# 3.0 (some future date)
Nice Ruby intro snippets! Similar interesting things btw can be found on
http://bigbold.com/snippets/
Pingback: What I love most about Ruby « iHack, therefore iBlog