codedecoder

breaking into the unknown…

ruby block : difference between yield and &block

5 Comments

block, proc and lambda , known as closure in computer terminology is a very important aspect of ruby. Here, I will try to explain block, its usage and implementation. We will write our code in ruby_block_demo.rb file and execute it from irb.

Let us create a class BlockDemo and define a welcome method, which will welcome the peoples passed to it as argument.

class BlockDemo

  def welcome(*person) # spat parameter is used here, 
  you can pass argument separeted by comma, which get converted into array
    person.each do |p|
      puts "Hi #{p}...how are you ?"
    end
  end

end

Let us call this method from irb on terminal

1.9.3-p194 :005 > load “/home/arun/ruby_block_demo.rb” # give absolute path of your ruby_block_demo.rb. read here about load and require if you get “LoadError :  can not load such file”
=> true
1.9.3-p194 :006 > demo_object=BlockDemo.new
=> #
1.9.3-p194 :007 > demo_object.welcome(“arun”, “kapil”)
Hi arun…how are you ?
Hi kapil…how are you ?

Here , we have incorporated the welcome message in the welcome method itself. What if we want to greet people from punjab in some other way and from chennai in some other way or so on. We can decouple the welcome message from welcome method using block concept.

A block can we wrapped in { } for single line of code or within do end for multiple line of code. The block is passed just after the method call with all its argument. Let us pass a block of code to welcome method from irb.

=> block with single line of code, so wrap it in { }

1.9.3-p194 :008 > demo_object.welcome(“arun”, “kapil”){“This is welcome message from block”}
Hi arun…how are you ?
Hi kapil…how are you ?

=> block with multiple line of code, so wrap it in do end.

You can use do end for single line of code also but { } look good for that

1.9.3-p194 :009 > demo_object.welcome(“arun”, “kapil”) do
1.9.3-p194 :010 >     puts “this is first welcome message from the block”
1.9.3-p194 :011?>    puts “this is second welcome message from the block”
1.9.3-p194 :012?>   end
Hi arun…how are you ?
Hi kapil…how are you ?

So you can see that, passing block doesn’t made any difference to the output of welcome method. This is because, the receiver method must implement the block through yield or &block keywords. So Let us modify the ruby_block_demo.rb code to implement the passed block.

=> block execution through yield.

class BlockDemo

  def welcome(*person)
    person.each do |p|
      puts "Hi #{p}...how are you ?"
      yield # this will execute the block passed to it
    end
  end

end

=> block execution through &block .

class BlockDemo

  def welcome(*person, &message)
    person.each do |p|
      puts "Hi #{p}...how are you ?"
      message.call() # this will execute the block passed to it
    end
  end

end

NOTE :
=>It is the & , which make the difference, the name can be anything like &message as above or &block or &xyz etc.
=>& will make message a Proc object
=>The & Implementation take more memory and time as compared to yield . I will explain the difference between the two and when to use which one when need arises. untill need arises we will use yield.

You can write either of above code in ruby_block_demo.rb and check the output in irb.

1.9.3-p194 :018 > load “/home/arun/ruby_block_demo.rb” # reload the file as change made to it.
=> true
1.9.3-p194 :019 > demo_object=BlockDemo.new
=> #
1.9.3-p194 :020 > demo_object.welcome(“arun”, “kapil”) do
1.9.3-p194 :021 > puts “this is first welcome message from the block”
1.9.3-p194 :022?> puts “this is second welcome message from the block”
1.9.3-p194 :023?> end
Hi arun…how are you ?
this is first welcome message from the block
this is second welcome message from the block
Hi kapil…how are you ?
this is first welcome message from the block
this is second welcome message from the block

So, you can see that
=> lines within do end block get executed
=> the block will get executed every time it is called. here we have put it in loop so it get executed twice.
=> So now welcome message is decoupled and you can pass different message when you need it.see another call below

1.9.3-p194 :024 > demo_object.welcome(“arun”, “kapil”) do
1.9.3-p194 :025 > puts “I welcome you in punjabi”
1.9.3-p194 :026?> puts “sasriya kaal…. 🙂 “
1.9.3-p194 :027?> end
Hi arun…how are you ?
I welcome you in punjabi
sasriya kaal…. 🙂
Hi kapil…how are you ?
I welcome you in punjabi
sasriya kaal…. 🙂

So, you can see we have now welcomed in Punjabi. This demonstrate the use of decoupling code with block.

=> Now what happen, if block is not passed. Let us see

1.9.3-p194 :028 > demo_object.welcome(“arun”, “kapil”) # calling the welcome method without block
Hi arun…how are you ?
LocalJumpError: no block given (yield)
from /home/arun/Documents/books/learn_ruby/ruby_block_demo.rb:6:in `block in welcome’
from /home/arun/Documents/books/learn_ruby/ruby_block_demo.rb:4:in `each’
from /home/arun/Documents/books/learn_ruby/ruby_block_demo.rb:4:in `welcome’
from (irb):28
from /home/arun/.rvm/rubies/ruby-1.9.3-p194/bin/irb:16:in `<main>’

O.K so, it will complain if block is not passed. Let us correct the code to make the block optional i,e it should not complain , if the user forget to pass it.

It can be handled by using block_given? condition. See below the modified code of ruby_block_demo.rb

=> block made optional.

class BlockDemo

  def welcome(*person)
    person.each do |p|
      puts "Hi #{p}...how are you ?"
      yield if block_given? # yield will be called only if block is passed.
    end
  end

end

Again call the method without block and see what happen

1.9.3-p194 :029 > load “/home/arun/ruby_block_demo.rb”
=> true
1.9.3-p194 :030 > demo_object=BlockDemo.new
=> #
1.9.3-p194 :031 > demo_object.welcome(“arun”, “kapil”)
Hi arun…how are you ?
Hi kapil…how are you ?

So, It is not complaining if we forget to pass the block.
Now, what if we want to pass argument to the block. well you can do it with yield considering it like any other method.

=> passing argument to block.

def welcome(*person)
   person.each do |p|
   yield(p,"Tamil") if block_given? # person name and his state passed as argument.
  end
end

Here we have passed two argument to yield. When you pass block it should define two parameter under the pipe ||
See on irb how to call the block now.

1.9.3-p194 :043 > load “/home/arun/ruby_block_demo.rb”
=> true
1.9.3-p194 :044 > demo_object=BlockDemo.new
=> #<BlockDemo:0xa414b3c>
1.9.3-p194 :045 > demo_object.welcome(“arun”, “kapil”) do |person, region|
1.9.3-p194 :046 >       puts “Hi #{person}…..how are you”
1.9.3-p194 :047?>      puts “I welcome you according to tradition of #{region} “
1.9.3-p194 :048?>   end
Hi arun…..how are you
I welcome you according to tradition of Tamil
Hi kapil…..how are you
I welcome you according to tradition of Tamil

So you can take it like the variables within || i,e |person, region| is the parameter signature of the method yield. when calling yield you should pass two argument.

DIFFERENCE BETWEEN yield and &block :

=> When to use yield over &block

Yield have high performance over &block in term of time and memory usage. This is illustrated here on stackoverflow.

NOTE : use yield unless you explicitly need &block due to any of the below reasons.

=> When to use &block over yield

Case 1: You need to manipulate the block before calling it.

Here, you need to understand that, yield is not a class, so you can’t create an object out of it. But &block internally represent an instance of Proc class, so you can apply all the available method of proc class to it.

yield just execute the block of code passed to it, it do not belong to any class. Let demonstrate it

change the code.

class BlockDemo

  def welcome(*person)
    person.each do |p|
      yield(p, "Tamil") if block_given?
    end
    puts yield.class
  end

end

Now rerun the code in irb

1.9.3p194 :017 > load “/home/arun/ruby_block_demo.rb”
=> true
1.9.3p194 :018 > block_demo = BlockDemo.new
=> #
1.9.3p194 :019 > demo_object.welcome(“arun”, “kapil”) do |person, reagion|
1.9.3p194 :020 > puts “we will welocme you later…let us see yield class”
1.9.3p194 :021?> end
we will welocme you later…let us see yield class
we will welocme you later…let us see yield class
we will welocme you later…let us see yield class
NilClass

So ,you can see that it printed NilClass.

Let us change the code in terms of &block.

class BlockDemo

  def welcome(*person, &message)
    person.each do |p|
      message.call(p, "Tamil") if block_given?
    end
    puts message.class
  end

end

rerun it again

1.9.3p194 :022 > load “/home/arun/ruby_block_demo.rb”
=> true
1.9.3p194 :023 > block_demo = BlockDemo.new
=> #
1.9.3p194 :024 > demo_object.welcome(“arun”, “kapil”) do |person, reagion|
1.9.3p194 :025 > puts “we will welocme you later…let us see yield class”
1.9.3p194 :026?> end
we will welocme you later…let us see yield class
we will welocme you later…let us see yield class
Proc

So you can see that &block is basically internally represent an object of Proc class. One another difference you can see that, here block is executed when call method is applied to it, so only two message is output. But in case of yield it is executed every time yield is encountered, so when you did yield.class, it get executed and printed the message one more time.

Case 2: You want to pass argument to yield depending on parameter needed by the block.

This case is just an illustration of benefit of &block being an object. Since you have all the method of Proc class available to your block, you can use them. We will take example of arity method

with arity you can decide, how many argument is needed by passed block, so you can pass the argument accordingly or do some other stuff before calling the block.

Let change the code as below :

class BlockDemo
  def welcome(*person, &message)
    no_of_argument = message.arity
    case no_of_argument
    when 1
      puts "I am sad only one parameter is passed"
      person.each do |p|
        message.call(p, "Tamil") if block_given?
      end
    when 2
      puts "wao..all the required parameter are passed"
      person.each do |p|
        message.call(p, "Tamil") if block_given?
      end
    else
      raise "invalid no of parameter"
    end
  end
end

Let us run the above code in irb with different number of argument.

1.9.3p194 :027 > load “/home/arun/ruby_block_demo.rb”
=> true
1.9.3p194 :028 > block_demo = BlockDemo.new
1.9.3p194 :031 > demo_object.welcome(“arun”, “kapil”) do |person, reagion|
1.9.3p194 :032 > puts “Hi #{person}…how are you”
1.9.3p194 :033?> end
wao..all the required parameter are passed
Hi arun…how are you
Hi kapil…how are you

1.9.3p194 :034 > demo_object.welcome(“arun”, “kapil”) do |person|
1.9.3p194 :035 > puts “Hi #{person}…how are you”
1.9.3p194 :036?> end
I am sad only one parameter is passed
Hi arun…how are you
Hi kapil…how are you

1.9.3p194 :063 > demo_object.welcome(“arun”, “kapil”) do
1.9.3p194 :064 > puts “Hi #{person}…how are you”
1.9.3p194 :065?> end
RuntimeError: invalid no of parameter
from /home/arun/Documents/books/learn_ruby/ruby_block_demo.rb:17:in `welcome’
from (irb):63
from /home/arun/.rvm/rubies/ruby-1.9.3-p194/bin/irb:16:in `’

So you can see that we have handled different block in different way depending on there parameters.

Case 3: You want to pass the block to some other method

This is case, where yield will not work and you have to use & block.
NOTE : yield cannot pass block to an other method

Let us modify our above code so that, the block pass only welcome message (not use puts as above while running in irb)and we will make another method to print the message.

class BlockDemo

  def welcome(*person, &message)
    no_of_argument = message.arity
    case no_of_argument
    when 1
      puts "I am sad only one parameter is passed"
      person.each do |p|
        print_message(&message)
      end
    when 2
      puts "wao..all the required parameter are passed"
      person.each do |p|
        print_message(&message)
      end
    else
      raise "ivalid no of parameter"
    end
  end
end

def print_message
  puts "welcome message will be printed by this method"
  puts yield("arun") if block_given?
end

Now, run this code on irb

1.9.3p194 :076 > load “/home/arun/ruby_block_demo.rb”
=> true
1.9.3p194 :077 > block_demo = BlockDemo.new
=> #
1.9.3p194 :078 > demo_object.welcome(“arun”) do |person|
1.9.3p194 :079 > puts “Hi #{person}…how are you”
1.9.3p194 :080?> end
I am sad only one parameter is passed
welcome message will be printed by this method
Hi arun…how are you

So here, block passed to first method, do not execute it itself but pass it to a second method. In such cases, you always need to use &block .

REFERENCES:

http://innig.net/software/ruby/closures-in-ruby

Advertisements

Author: arunyadav4u

over 7 years experience in web development with Ruby on Rails.Involved in all stage of development lifecycle : requirement gathering, planing, coding, deployment & Knowledge transfer. I can adept to any situation, mixup very easily with people & can be a great friend.

5 thoughts on “ruby block : difference between yield and &block

  1. Amazing! Its truly amazing article, I have got much clear idea about from this
    post.

  2. Thank you very much for this very very good written post. I’ve learned much.

  3. Thanks, Arun! Good stuff. Now I get it.

  4. this was an awesome explanation, thanks!!

  5. Good Explanation 🙂

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s