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!" endThe 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 endI 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 endand 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 endThe 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 endThere 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 endFinally 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 endand calling it:
numbers = [1, 2, 3, 4] numbers.iterate_with_index! do |number, index| puts index endreturns:
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
Insightful post, thank you!
AntwortenLöschen