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.newandlambdain this example are ways to create a closure, but semantics of the closures thus created are different with respect to thereturnstatement.
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"
Pingback: Ruby metaprogramming step-by-step « ytoh’s blog
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.