Ruby Language Blocks and Closures

Sang Shin, sang.shin@sun.com, www.javapassion.com/rubyonrails



The feature of Ruby block is one of the differences between Java and Ruby - there is no comparable feature in Java.)  Ruby blocks allow you to encapsulate code and send it to a method for any kind of usage that method wants to use it for.  Any method in Ruby can have a block attached to it. 

Acknowledgment: The Closure exercises are borrowed from Paul Cantrell with his permission.  The original examples he wrote can be seen from here.

Expected duration: 120 minutes (excluding homework)


Software Needed

Before you begin, you need to install the following software on your computer.  The installation instruction of JDK and NetBeans is described in  Exercise 0 below.


OS platforms you can use


Change Log


Things to be done (By Sang Shin)


Lab Exercises


Exercise 1: Ruby Blocks


Learning points
Tasks to be performed:
  1. Open, build, and run "RubyBlock_TwoFormats" sample application
  2. Open, build, and run "RubyBlock_yieldExecuteSuppliedBlock" sample application
  3. Open, build, and run "RubyBlock_yieldWithParameters" sample application
  4. Open, build, and run "RubyBlock_yieldWithParameters2" sample application

(1.1) Open, build, and run "RubyBlock_TwoFormats" sample application


1. Open "RubyBlock_TwoFormats" sample application.
2. Study the code.

#
# Block can be represented in two different formats
#
 
puts "----First format of code block containing code fragment between { and }"
[1, 2, 3].each { |n| puts "Number #{n}" }

puts "----Second format of code block containing code fragment between do and end"
[1, 2, 3].each do |n|
  puts "Number #{n}"
end

Explanation of the code: The each is a method of Array class as shown Array Ruby doc.  A block is attached (supplied) to the each method.  The each method calls the block once for each element in self, passing that element as a parameter. This is an example of a built-in method of Ruby language which takes a block.  In the subsequent steps, you are going to define your method which takes a block.

For your own study: Move the mouse to the each and press CTRL+Space and read the document of each method.



3. Run the project.
4. Study the result.

----First format of code block containing code fragment between { and }
Number 1
Number 2
Number 3
----Second format of code block containing code fragment between do and end
Number 1
Number 2
Number 3

                                                                                                                                                 return to top of exercise

(1.2) Open, build, and run "RubyBlock_yieldExecuteSuppliedBlock" sample application


In the previous step, you've seen an example in which a built-in method, each, of Array class of Ruby language takes a block.  In this and next steps, you are going to define your own method in uour own class, which takes a block.

1. Open "RubyBlock_yieldExecuteSuppliedBlock" sample application.
2. Study the code.

#
# The goal of this exercise is to see how
# a supplied block is executed through "yield"
# keyword.
#
 
puts "----Define MyClass which invokes yield"
class MyClass
  def command()
    puts "This is a message from the command method of MyClass "
   
    # yield will execute the attached block to the method
    yield()   
  end       
end

puts "----Create object instance of MyClass"
m = MyClass.new

puts "----Call command method of the MyClass passing a block"
m.command {puts "Hello World!"}

puts "----Call command method of the MyClass passing a block"
m.command {puts "Life is good!"}

puts "----Call command method of the MyClass passing a block"
# Sometimes you will see blocks that seem 'nested', for example, here
# the 'command' yields to a block, who calls method 'each', who yields
# (again!) to an inner block"
m.command do
  [1, 2, 3].each do |n|
    puts "Number #{n}"
  end
end

puts "----Call command method of the MyClass passing a block"
m.command { [1, 2, 3].each { |n| puts "Number #{n}" } }

Explanation of the code: When command method of the MyClass is invoked, it is attached (or supplied/passed/sent) with a block, for example, {puts "Hello World!"}.  Now this attached block will be executed where the yield() method is called within the command method.  Hence, the result will be

This is a message from the command method of MyClass
Hello World!

For your own study: Move the cursor over yield and press CTRL+Space. Observe the document dialog box of the yield gets displayed.



3. Run the project.
4. Study the result.
----Define MyClass which invokes yield
----Create object instance of MyClass
----Call command method of the MyClass passing a block
This is a message from the command method of MyClass
Hello World!
----Call command method of the MyClass passing a block
This is a message from the command method of MyClass
Life is good!
----Call command method of the MyClass passing a block
This is a message from the command method of MyClass
Number 1
Number 2
Number 3
----Call command method of the MyClass passing a block
This is a message from the command method of MyClass
Number 1
Number 2
Number 3

                                                                                                                                                 return to top of exercise

(1.3) Open, build, and run "RubyBlock_yieldWithParameters" sample application


1. Open "RubyBlock_yieldWithParameters" sample application.
2. Study the code.

puts "----Define MyClass that yields the passed code block"
class MyClass
 
  def command1()
    # yield will execute the supplied block
    yield(Time.now)   
  end       
 
  def command2(my_parameter)
    puts "**Star command says: "
   
    # yield will execute the supplied block
    yield(my_parameter)   
  end       
end

puts "----Create an object instance of MyClass"
m = MyClass.new

puts "----Call command1 method of the MyClass"
m.command1() {|x| puts "Current time is #{x}"}

puts "----Call command2 method of the MyClass"
m.command2(1) {|x| puts "Hello World! is called with number #{x}" }
m.command2(2) {|i| puts "Number #{i} is passed."}
m.command2(3) {|i| puts "Number #{i} is passed."}
m.command2("something") {|x| puts x}
m.command2("something") {|t| puts t}

3. Run the project.
4. Study the result.

----Define MyClass that yields the passed code block
----Define MyClass that yields the passed code block
----Create an object instance of MyClass
----Call command1 method of the MyClass
Current time is Mon Jul 14 15:23:20 -0400 2008
----Call command2 method of the MyClass
**Star command says:
Hello World! is called with number 1
**Star command says:
Number 2 is passed.
**Star command says:
Number 3 is passed.
**Star command says:
something
**Star command says:
something

Explanation of the code: The block can take parameters, which are passed from the yield(<parameters>) method.  In this example, the yield(my_parameter) method passes my_parameter as a parameter.  In the block, the passed parameters(or arguments) are represented as variables between the two vertical bars, |<variables>|.

                                                                                                                                                 return to top of exercise


(1.4) Open, build, and run "RubyBlock_yieldWithParameters2" sample application


1. Open "RubyBlock_yieldWithParameters2" sample application.
2. Study the code.

puts "---Define a method called testyield"
def testyield
  yield(1000, "Sang Shin")
  yield("Current time is", Time.now)
end

puts "----Call testyield method"
testyield { |arg1, arg2| puts "#{arg1} #{arg2}" }

3. Run the project.
4. Study the result.

---Define a method called testyield
----Call testyield method
1000 Sang Shin
Current time is Mon Jun 30 09:14:56 -0400 2008

Explanation of the code: When the yield(1000, "Sang Shin") is invoked within the testyield method, two parameters, 1000 and "Sang Shin" are passed to the block as two arguments, which are represented as arg1 and arg2.


                                                                                                                                                 return to top of exercise


Exercise 2: Proc object and lambda


Learning points
Tasks to be performed in this exercise
  1. Open, build, and run "RubyBlock_Proc_CreateAndCall" sample application
  2. Open, build, and run "RubyBlock_Proc_AsReturnValue" sample application
  3. Open, build, and run "RubyBlock_Proc_AsArgument" sample application
  4. Open, build, and run "RubyBlock_lambda" sample application
  5. Open, build, and run "RubyBlock_lambda_diffFromProcNew1" sample application
  6. Open, build, and run "RubyBlock_lambda_diffFromProcNew2" sample application

(2.1) Open, build, and run "RubyBlock_Proc_CreateAndCall" sample application


1. Open "RubyBlock_Proc_CreateAndCall" sample application.
2. Study the code.

# Proc objects are blocks of code that have been converted
# to callable objects.

puts "----Create a Proc object and call it"
say_hi = Proc.new { puts "Hello Sydney" }
say_hi.call

puts "----Display the class of Proc object"
puts say_hi.class

puts "----Create another Proc object and call it"
Proc.new { puts "Hello Boston"}.call

3. Run the project.
4. Study the result.

----Create a Proc object and call it
Hello Sydney
----Display the class of Proc object
Proc
----Create another Proc object and call it
Hello Boston

                                                                                                                                                 return to top of exercise


(2.2) Open, build, and run "RubyBlock_Proc_CreateAndCall2" sample application


Learning point: The proc method converts a block into a Proc object.

1. Open "RubyBlock_Proc_CreateAndCall" sample application.
2. Study the code.

puts "----Define a method that returns a Proc object"
def my_hello
 return proc { |x, y| puts "Hello #{x} #{y}" }
end

puts "----Execute the block"
my_variable = my_hello
my_variable.call("Young Shin", Time.now)

3. Run the project.
4. Study the result.

----Define a method that returns a Proc object
----Execute the block
Hello Young Shin Tue Jul 15 18:01:33 -0400 2008

                                                                                                                                                 return to top of exercise


(2.3) Open, build, and run "RubyBlock_Proc_AsReturnValue" sample application


Learning point: A method returns a Proc object as a return value.

Learning point: This code also shows how Proc object is used as a Closure.  Note that  Proc objects (Procs) are blocks of code that have been bound to a set of local variables.  Once bound, the code may be called in different contexts and still access those variables.  In this example, when gen_times(3) is invoked, the loal variable factor is to 3 and the code remembers that.  So times3.call(12) is called later on, the code will use the value of the factor variable, 3.

1. Open "RubyBlock_Proc_AsReturnValue" sample application.
2. Study the code.

puts "----Define a method that returns Proc object as a return value"
def gen_times(factor)    
  Proc.new {|n| n*factor }  
end

puts "----Assign Proc object to local variables"
times3 = gen_times(3)               # Block has factor variable set to 3
times5 = gen_times(5)               # Block has factor variable set to 5

puts "----Execute the code block passing a parameter"
puts times3.call(12)                    #=> 36 because 12 * 3 (factor) = 36
puts times5.call(5)                      #=> 25 because 5 * 5 (factor) = 25
puts times3.call(times5.call(4))   #=> 60 because (4 * 5  )  * 3 (factor) = 60

3. Run the project.
4. Study the result.

----Define a method that returns Proc object as a return value
----Assign Proc object to local variables
----Execute the code block passing a parameter
36
25
60



                                                                                                                                                 return to top of exercise

(2.4) Open, build, and run "RubyBlock_Proc_AsArgument" sample application


Learning point: Proc object can be passed around as an arugment.

1. Open "RubyBlock_Proc_AsArgument" sample application.
2. Study the code.

puts "----Create Proc objects"
proc_a = Proc.new {|x| puts x}
proc_b = Proc.new {|x| puts x.reverse }

puts "----Define a method that receives Proc object as an argument, it also calls block"
def foo (proc_obj, str)
  proc_obj.call(str)
end
 
puts "----Call a method that passes a Proc object as an arugment"
foo(proc_a, 'Sang Shin')
foo(proc_b, 'Sang Shin')

3. Run the project.
4. Study the result.

----Create Proc objects
----Define a method that receives Proc object as an argument
----Call a method that passes a Proc object as an arugment
Sang Shin
nihS gnaS


                                                                                                                                                 return to top of exercise



(2.5) Open, build, and run "RubyBlock_lambda" sample application


1. Open "RubyBlock_lambda" sample application.
2. Study the code.

# Equivalent to Proc.new, except the Proc object built using
# lambda checks the number of parameters passed when
# called.

puts "----Create a Proc object through lambda and call it"
say_hi = lambda { puts "Hello Sydney" }
say_hi.call

puts "----Create another Proc object through lambda and call it"
lambda { puts "Hello Boston"}.call

3. Run the project.
4. Study the result.

----Create a Proc object and call it
Hello Sydney
----Create another Proc object and call it
Hello Boston

                                                                                                                                                 return to top of exercise


(2.6) Open, build, and run "RubyBlock_lambda_diffFromProcNew1" sample application


1. Open "RubyBlock_lambda_diffFromProcNew1" sample application.
2. Study the code.

# The resulting Proc object (from using lambda) checks the
# number of parameters passed when called, if not match,
# throws an exception

puts "----Create Proc object using lambda"
lamb = lambda {|x, y| puts x + y}

puts "----Create Proc object using Proc.new"
pnew = Proc.new {|x, y| puts x + y}

puts "----Send 3 arguments, should work"
pnew.call(2, 4, 11)

puts "----Send 3 arguments, throws an ArgumentError"
lamb.call(2, 4, 11)

3. Run the project.
4. Study the result.

----Create Proc object using lambda
----Create Proc object using Proc.new
----Send 3 arguments, should work
6
----Send 3 arguments, throws an ArgumentError
C:\handsonlabs\ruby_blocks\samples\RubyBlock_lambda_diffFromProcNew1\lib\main.rb:15:in `call': wrong number of arguments(3 for 2) (ArgumentError)
        from C:\handsonlabs\ruby_blocks\samples\RubyBlock_lambda_diffFromProcNew1\lib\main.rb:15



                                                                                                                                                 return to top of exercise

(2.7) Open, build, and run "RubyBlock_lambda_diffFromProcNew2" sample application


1. Open "RubyBlock_lambda_diffFromProcNew2" sample application.
2. Study the code.

# Another difference between lambda and Proc.new is in the way
# returns are handled from the Proc. A return from Proc.new
# returns from the enclosing method (acting just like a return
# from a block). A return from lambda acts more conventionally,
# returning to its caller.

def foo
  f = Proc.new { return "return from foo from inside proc" }
  f.call # control leaves foo here
  return "return from foo"
end

def bar
  f = lambda { return "return from lambda" }
  f.call # control does not leave bar here
  return "return from bar"
end

puts foo # prints "return from foo from inside proc"
puts bar # prints "return from bar"

3. Run the project.
4. Study the result.

return from foo from inside proc
return from bar

                                                                                                                                                 return to top of exercise



Summary

In this exercise,  you have learned how to use a block of Ruby language.

                                                                                                                                                 return to the top



Exercise 3: Ruby & (Ampersand) Operator


Learning points
Tasks to be performed:
  1. Open, build, and run "RubyBlock_Ampersand_blockToProc" sample application
  2. Open, build, and run "RubyBlock_Ampersand_ProcToBlock" sample application

(3.1) Open, build, and run "RubyBlock_Ampersand_blockToProc" sample application


1. Open "RubyBlock_Ampersand_blockToProc" sample application.
2. Study the code.

# The ampersand operator can be used to explicitly convert
# between blocks and Procs in a couple of cases. It is worthy
# to understand how these work.

# If an ampersand (&) is prepended to the last argument in
# the argument list of a method, the block attached to this
# method is converted to a Proc object and gets assigned to
# that last argument.

puts "----Define a method that receives a block as a Proc object"
def m1(a, &f)
  f.call(a)
end

puts "----Define a method that receives a block"
def m2(a)
  yield a
end

puts "----Call a method passing a block"
m1('Seoul') { |x| puts "I dream of #{x}" }

puts "----Call a method passiing a block"
m2('Milan') { |x| puts "I dream of #{x}" }

3. Run the project.
4. Study the result.

----Define a method that receives a block as a Proc object
----Define a method that receives a block
----Call a method passing a block
I dream of Seoul
----Call a method passiing a block
I dream of Milan

                                                                                                                                                 return to top of exercise


(3.2) Open, build, and run "RubyBlock_Ampersand_ProcToBlock" sample application


1. Open "RubyBlock_Ampersand_ProcToBlock" sample application.
2. Study the code.

# Another use of the ampersand is the other-way conversion -
# converting a Proc into a block. This is very useful because
# many of Ruby’s great built-ins, and especially the iterators,
# expect to receive a block as an argument, and sometimes it’s
# much more convenient to pass them a Proc.

puts "----Create a Proc object"
say_hi = Proc.new { |x| puts "#{x} Hello Korea" }
say_hi.call

puts "----Define a method which expects Proc object as an argument"
def do_it_with_call(&f)
  f.call rand(10)
end

puts "----Define a method which expects a block NOT Proc object"
def do_it_with_yield
  yield rand(10) if block_given?
end

do_it_with_call  &say_hi  # => 6 Hello Korea

do_it_with_yield &say_hi  # => 2 Hello Korea

3. Run the project.
4. Study the result.

----Create a Proc object
 Hello Korea
----Define a method which expects Proc object as an argument
----Define a method which expects a block NOT Proc object
6 Hello Korea
3 Hello Korea

                                                                                                                                                 return to top of exercise


Exercise 4: Blocks and Iterators


Learning points
Tasks to be performed:
  1. Open, build, and run "RubyBlock_Iterator" sample application
  2. Open, build, and run "RubyBlock_Iterator2" sample application

(4.1) Open, build, and run "RubyBlock_Iterator" sample application


1. Open "RubyBlock_Iterator" sample application.
2. Study the code.

puts "----Define a method which takes block as a Proc object argument"
def my_method(count, &my_block)
  value = 1
 
  # Execute the block "count" times while updating the vlaue i
  1.upto(count) do |i|
    value = value * i
    my_block.call(i, value)
  end
 
end

puts "----Invoke the method passing a code block"
my_method(5) do |i, result|
  puts "my_method(#{i}) = #{result} "
end

3. Run the project.
4. Study the result.

----Define a method which takes block as a Proc object argument
----Invoke the method passing a code block
my_method(1) = 1
my_method(2) = 2
my_method(3) = 6
my_method(4) = 24
my_method(5) = 120


                                                                                                                                                 return to top of exercise


(4.2) Open, build, and run "RubyBlock_Iterator2" sample application


1. Open "RubyBlock_Iterator2" sample application.
2. Study the code.

# An example where block is passed as an initialization argument
class Repeater
 
  def initialize(&block)
   
    # Save the Proc object into an instance variable
    @block = block
    @count = 0
  end
 
  def repeat
    @count +=1
    @block.call(@count)
  end
end

puts "---- Pass a block as an intialization argument"
repeater = Repeater.new do |count|
  puts "You called me #{count} times"
end

puts "---- Call the repeat method 5 times"
5.times do
  repeater.repeat
end

3. Run the project.
4. Study the result.

---- Pass a block as an intialization argument
---- Call the repeat method 5 times
You called me 1 times
You called me 2 times
You called me 3 times
You called me 4 times
You called me 5 times


                                                                                                                                                           return to top of exercise


Exercise 5: Closure Basics


Learning points
Tasks to be performed
  1. Open, build, and run "RubyClosure_example0" sample application
  2. Open, build, and run "RubyClosure_example1" sample application
  3. Open, build, and run "RubyClosure_example2" sample application
  4. Open, build, and run "RubyClosure_example3" sample application
  5. Open, build, and run "RubyClosure_example4" sample application

(5.0) Open, build, and run "RubyClosure_example0" sample application


1. Open "RubyClosure_example0" sample application.
2. Study the code.

# The interesting thing is that it's more than just a chunk of code.
# Associated with a block (and hence a Proc object) is all the context
# in which the block was defined: the value of self, and the methods,
# variables, and constants in scope. Part of the magic of Ruby is
# that the block can still use all this original scope information
# even if the environment in which it was defined would otherwise
# have disappeared. In other languages, this facility is called a
# closure.

# Let's look at a contrived example. This example uses the method proc,
# which converts a block to a Proc object.

def nTimes(aThing)
  return proc { |n| aThing * n }
end

puts "----Create a Proc object by calling nTimes method. "
# The aThing is set to value 23 in a block.  Even if nTimes method is
# finished, the aThing variable in the block still contains 23..
p1 = nTimes(23)

puts "----Execute the block"
# Now execute the block.  Note that the aThing is still set to
# 23, so the results is 69 and 92
puts p1.call(3)     #     69
puts p1.call(4)     #     92

puts "----Create a Proc object by calling nTimes method. "
# The aThing is set to value "Hello " in a block.  Even if nTimes method is
# finished, the aThing variable in the block still contains "Hello ".
p2 = nTimes("Hello ")

puts "----Execute the block"
# Now execute the block.  Note that the aThing is still set to
# "Hello ", so the results is "Hello Hello Hello "
puts p2.call(3)     #     "Hello Hello Hello "

3. Run the project.
4. Study the result.

----Create a Proc object by calling nTimes method.
----Execute the block
69
92
----Create a Proc object by calling nTimes method.
----Execute the block
Hello Hello Hello

                                                                                                                                                 return to top of exercise

(5.1) Open, build, and run "RubyClosure_example1" sample application


1. Open "RubyClosure_example1" sample application.
2. Study the code.

# Blocks are like closures, because they can refer to variables from
# their defining context:

def thrice
    yield
    yield
    yield
end

x = 5
puts "value of x before: #{x}"

thrice { x += 1 }
puts "value of x after: #{x}"

3. Run the project.
4. Study the result.

value of x before: 5
value of x after: 8

                                                                                                                                                 return to top of exercise

(5.2) Open, build, and run "RubyClosure_example2" sample application


1. Open "RubyClosure_example2" sample application.
2. Study the code.

# A block refers to variables in the context it was defined, not the
# context in which it is called:

def thrice_with_local_x
    x = 100
    yield
    yield
    yield
    puts "value of x at end of thrice_with_local_x: #{x}"
end

x = 5
thrice_with_local_x { x += 1 }
puts "value of outer x after: #{x}" #=> 8

3. Run the project.
4. Study the result.

value of x at end of thrice_with_local_x: 100
value of outer x after: 8

                                                                                                                                                 return to top of exercise



(5.3) Open, build, and run "RubyClosure_example3" sample application


1. Open "RubyClosure_example3" sample application.
2. Study the code.

# A block only refers to *existing* variables in the outer context; if
# they don't exist in the outer, a block won't create them there:

def thrice
    yield
    yield
    yield
end

thrice do # note that {...} and do...end are completely equivalent
    y = 10
    puts "Is y defined inside the block where it is first set?"
    puts "Yes." if defined? y
end

puts "Is y defined in the outer context after being set in the block?"
puts "No!" unless defined? y

3. Run the project.
4. Study the result.

Is y defined inside the block where it is first set?
Yes.
Is y defined inside the block where it is first set?
Yes.
Is y defined inside the block where it is first set?
Yes.
Is y defined in the outer context after being set in the block?
No!

                                                                                                                                                 return to top of exercise


(5.4) Open, build, and run "RubyClosure_example4" sample application


1. Open "RubyClosure_example4" sample application.
2. Study the code.

# OK, so blocks seem to be like closures: they are closed with respect to variables defined in the context
# where they were created, regardless of the context in which they're called.
#
# But they're not quite closures as we've been using them, because we have no way to pass them around:
# "yield" can *only* refer to the block passed to the method it's in.
#
# We can pass a block on down the chain, however, using &:

def thrice
    yield
    yield
    yield
end

def six_times(&block)
    thrice(&block)
    thrice(&block)
end

x = 4
six_times { x += 10 }
puts "value of x after: #{x}"

3. Run the project.
4. Study the result.

value of x after: 64

                                                                                                                                                 return to top of exercise


Homework Exercise (for people who are taking Sang Shin's "Ruby/JRuby/Rails Development online course")


1. The homework is to write your own Ruby application as described below.  The goal of this homework is to test your understanding on Ruby block, which is covered in Exercise 1 above.
2. Send the following files to railshomeworks@sun.com with Subject as RailsHomework-ruby_blocks.


                                                                                                                                                 return to the top