Ruby blocks, lambda and Procs

Block

Ruby code block are chunks of code surrounded by do and end keywords (or single line block with curly braces). Blocks can take arguments. The arguments are declared surrounding variable names by pipe symbols. They can be associated with method calls and evaluated using yield. Passing arguments is accomplished by passing arguments to yield. Any method can be called with a block as an implicit argument. So for example:

# implicit block evaluation
def m1
  yield
end

# passing arguments to implicit block
def m2( param )
  yield param
end

# assigning a name to an implicit block
def m3( param, &block )
  block.call param
end

m1 { puts 'hello' }
# => "hello"

m2( 'hello' ) { |x| puts x }
# => "hello"

m3( 'hello' ) do |x|
  3.times { puts x }
end
# => "hello"
# => "hello"
# => "hello"

In the above example we can see how are blocks associated with method calls and how are blocks evaluated inside a method. In the m3 method call we can see how multi line blocks are associated with method calls.

Q: Whoa! Where is the yield in m3, hmm? And what is the meaning of the ampersand before the parameter block?

You got me :) The yield is replaced by block.call because we supplied a name for the block being associated (and a very unimaginative one: block) and thanks to that by the time the block gets to the method body it’s no longer a block. It’s actually a Proc. In m1 and m2 the block is anonymous and we evaluate it by calling yield. If we want to give a name to the block (by putting an ampersand before the name of the methods last parameter) we get a reference to it wrapped in a Proc object. And to evaluate a Proc you need to call it’s call method.

The m3 example is interesting in another way also. It shows how blocks handle scope of variables. The block sees the variables in the context (scope) it was declared in. The block { puts x } sees the variable x declared outside of its scope and therefore can print it. And blocks are generous and can provide that kind of scope transcending service to anyone — but only if they go through a self sacrifice and change into a Proc!

Proc

A Proc can be created by associating a block to the call of Proc.new (actually associating a block with any method call does the trick). Proc is a block associated with a context. So for example if we have a local variable say foo and we use it in a block and send the block to a method which automatically converts the block to a Proc then the formally local variable foo can be accessed in the new scope of the method. Pretty cool, huh?

def bar
  yield( 10 )
  puts var # bam! this throws an error
end

var = 1
bar { |value| var = value }
# => 10
# => NameError: undefined local variable or method `var' for main:Object
#            from (irb):3:in `bar'
#            from (irb):6

In the above example we declare a method bar which cannot access the variable var which is defined later in the scope but using a block we can assign a value to it without being able to access it directly (hence the NameError). Since the start of a method (or class) definition opens a new context we cannot assign the value of var in a method and see it change in the outer scope without the cool goodness of Procs. So only an “insane” person would try something like this:

var = 1
def bar
  var = 10
end
bar
puts var
# => 1

And expect var to be 10.

Lambda

Lambda is a Kernel method (so we should write it with a lowercase l – lambda) a call to which is equivalent to Proc.new. Except that a lambda returns a Proc which checks the number of parameters passed when called. If the number of parameters is wrong you get a warning.

l = lambda {|x| 3.times {puts x}}
l.call "hi","you"
# => (irb):2: warning: multiple values for a block parameter (2 for 1)
# => "hi"
# => "you"
# => "hi"
# => "you"
# => "hi"
# => "you"

Lambda vs Proc

From Wikipedia

Both Proc.new and lambda in this example are ways to create a closure, but semantics of the closures thus created are different with respect to the return statement.

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"

2 comments
  1. banisterfiend said:

    very nice article :) but one thing, you say that in the m3 method the ‘block is no longer a block. Unfortunately this is not quite true. You can still yield to the block even though it is also explicitly passed as a parameter; block_given? works also. In fact the only difference between implicit and explicit (&block) parameters is that in the explicit case the block is also converted to a proc. In fact explicit block passing is identical to this:

    def m4
    p = Proc.new
    # can now use the implicit block as the proc called ‘p’
    end

    This is also why passing a block explictly (&block) is slower than passing implicitly; because the extra conversion from block to proc and proc creation must take place. :)

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 )

Connecting to %s

Follow

Get every new post delivered to your Inbox.