Montag, 11. Februar 2013

Yield your block in Ruby

In Ruby there are some topics which are not often discussed and even some developer not really know about. One of them are closures.
The Ruby closures are called blocks, Procs and lambdas. They enclose logic, which can be invoked outside of its immediate scope.
Every Ruby developer already used a block when running the well known Array#collect! or Hash#select!. An easy to understand (and 100% true) example:
languages = ["Ruby", "Python", "Javascript"]
languages.collect! do |language|
  "#{language} is awesome!"
end
The introduction so far. But how does the Proc behind (e.g. Array#collect!) work, how to do something similar and when to use it?
To illustrate the functionality of such closure I start with the Proc itself:
class Array
  def power!(&block)
    self.each_with_index do |number, index|
      self[index] = number * number
      block.call(self[index])
    end
  end
end
I re-opened the Array class and added the power! method. I sticked to Ruby conventions and put a bang at the end (if you don't know why, read about the bang convention). The method itself expects a block (or rather enclosed logic) to be passed. That is why there is an ampersand before the block parameter. Inside it only iterates over its items, squares each and calls the block on the result. Quick & simple.
The Proc is called:
numbers = [1, 2, 3, 4]
numbers.power! do |number|
  puts number
end
and prints:
1
4
9
16
 => [1, 4, 9, 16]
Well. Simple but pretty static. The call of "puts" is injected into the scope of the method "power!" by putting it into the block. Let's enhance the same example and rename the method:
class Array
  def iterate!(&block)
    self.each_with_index do |number, index|
      self[index] = block.call(number)
    end
  end
end
The method "iterate!" is pretty comparable to the stuff Array#collect! does. It iterates over each item and stores the result of the called block. In a conclusion "iterate!" offers much more dynamics. We also could square:
numbers = [1, 2, 3, 4]
numbers.iterate! do |number|
  number * number
end
There is a keyword known for calling the block. Its name is "yield". Using it you don't pass a "&block" to the method. The Array#iterate! would look like:
class Array
  def iterate!
    self.each_with_index do |number, index|
      self[index] = yield(number)
    end
  end
end
Finally I want to point out that you also can pass as much as parameters as you need to your closure. An example with two parameters would be:
class Array
  def iterate_with_index!(&block)
    self.each_with_index do |number, index|
      self[index] = block.call(number, index)
    end
  end
end
and calling it:
numbers = [1, 2, 3, 4]
numbers.iterate_with_index! do |number, index|
  puts index
end
returns:
0
1
2
3
 => [nil, nil, nil, nil]
I use blocks to keep my code DRY and to achieve more readability. And there will be a point you can't ignore them.

Supported by Ruby 1.9.3

1 Kommentar: