開発
let’s use blocks if using_ruby?
denvazh
As usual: Why?
For most of the ruby projects I’ve helped to develop, I noticed an ultimate
lack of blocks. In fact, contructions and helper methods available because of
ruby itself (or included gems) was only used. That is why I like to explain a
bit about them and show how useful they are.
Background
In broad terms, closure is a function or reference to a function together with a referencing environment,
which is a table storing a reference to each of the non-local variables of that function. Implementations for each programming languages are quite different in terms of used constructions and syntax, but all of the share similar idea behind.
What about Ruby?
In case of Ruby, developer have to use blocks, procs and lambdas. Those who already tried ruby, should be fairly familiar with blocks.
> [1,2,3,4,5,6].each_with_index { |x,y| puts x*y } 0 2 6 12 20 30
Same behaviour is true for code enclosed in do-end block.
There is more…
There are several differences one might like to know about before actually start using this knowledge in the open wild environment 🙂
- Proc
> p = Proc.new { |x| puts x*(x/2) } [1,2,3,4,5,6].each(&p) 0 2 3 8 10 18
> l = lambda { |x| puts x*(x/2) } [1,2,3,4,5,6].each(&l) 0 2 3 8 10 18
If Proc or Lambda instance is passed with & it tells ruby to turn proc/lamdba to block. Code within the block gets executed when its directly called ( for example l.call(50) ) or called from within other function it was passed to.
It is also worth mentioning, that blocks are not objects – they just a part of method call, whereas Procs and Lambdas are objects.
All little tiny details are explained in a very easy to understand blog post
Ok…and how to use this great knowledge?
Let’s consider the following small example. Suppose we would like to have a list of items. Typically we would define structre we like in the class. For this example it would have only one field which is initialized as array when instance created.
Normally, we would define separate method for certain actions we would like to have. However, in the long run we might end up with a huge number of them (and it would look like PHP).
On the other hand, using block allows us to modify method behavior without necessarily chaning its body.
#!/usr/bin/env ruby class BlockSample attr :storage def initialize @storage ||= [] end def add_item(name=nil,count=0,&block) if block_given? b = lambda &block end if (name && count > 0) item = {:name => name, :count => count, :block => b} @storage << item return item else puts "[Warning] count for '#{name}' cannot be negative" end end def query_storage items ||= [] if (@storage) @storage.each do |s| if (s[:block]) res = s[:block].call(s[:name],s[:count]) items.push(res) else items.push("#{s[:name]}_#{s[:count]}") end end end return items end end sample = BlockSample.new sample.add_item('stallion') sample.add_item('horse', 2) { |k,v| k+="_#{v*v}" } sample.add_item('unicorn', 100) sample.add_item('person', 20) sample.add_item('poni', 50) { |k,v| k+="_#{2*v}" } status = sample.query_storage puts status
When executed it would give the following result:
[Warning] count for 'stallion' cannot be negative horse_4 unicorn_100 person_20 poni_100
Normally, it is possible to pass along block from method to method as a last parameter (method_name(arg1,arg2,&block)). For instance, this how Rails helper function link_to behaves.
In the case above, we might want to store all arguments and block, however we cannot just assign whole block body to some variable. Using lambda it is possible to store block body in a method and then use it normally, for example store it in hash and then push this hash to class variable @storage array.
When it is necessary to use stored items (otherwise, why bother to store them in the first place:-) ) we use another method where actually use stored block (&block.call). In this example, we call block and pass there parameters we already have. At this point block code gets evaluated and executed.
puts conclusion if finished?
Personally, I like learning new things or widening my knowledge for something I already aware of, so I hope this would help somebody to see that there are always multiple ways of achieving expected result.