Eyes, JAPAN Blog > let’s use blocks if using_ruby?

let’s use blocks if using_ruby?

denvazh

この記事は1年以上前に書かれたもので、内容が古い可能性がありますのでご注意ください。

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
    
  • Lambda
  • > 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.

Comments are closed.