Blocks

From RubySpec

Jump to: navigation, search

A code block in Ruby is an executable grouping of statements allowing you to pass code to other methods or save it for later use. They behave much like lambda, closures, and/or callbacks in other languages.

Contents

Block Syntax

A block is a set of statements and expressions between braces or a do/end pair. The block can start with an argument list in vertical bars. A code block may appear only directly after a method invocation. The start of the block must be on the same logical line as the end of the invocation.

Braces have high precedence; do has low precedence. There's a convention on Ruby community that braces are used for one-liner blocks when do/end pairs are used for multiline blocks. If the method invocation contains parameters that are not enclosed in parentheses, the brace form of a block will bind to the last parameter, not to the overall invocation. The do form will bind to the invocation.

# do/end:
invocation do | a1, a2 |
end

# braces:
invocation { | a1, a2 |
}

# will bind block to param1, probably not intended
invocation param1 { | a1, a2 |
}

# this bind to the correct invocation
invocation param1 do | a1, a2 |
end

Within the body of the invoked method, the code block can be called using the yield keyword. Parameters passed to the yield will be assigned to the arguments in the block. A warning will be generated if yield passes multiple parameters to a block that takes just one. The return value of the yield is the value of the last expression evaluated in the block or the value passed to a next statement executed in the block.

A block is a closure; it remembers the context in which it was defined and it uses that context whenever it is called. The context includes the value of self, the constants, class variables, local variables and any captured block.

class Holder
  CONST = 100
  def call_block
    a = 101
    @a = 102
    @@a = 103
    yield
  end
end

class Creator
  CONST = 0
  def create_block
    a = 1
    @a = 2
    @@a = 3
    proc do
      puts "a = #{a}"
      puts "@a = #@a"
      puts "@@a = #@@a"
      puts yield
    end
  end
end

block = Creator.new.create_block { "original" }
Holder.new.call_block(&block)

This produces:

a = 1
@a = 2
@@a = 3
original

Proc Objects

Ruby blocks are not objects per se, but can be converted into objects of class Proc. There are three ways of converting a block into a Proc object.

1. By passing a block to a method whose last parameter is prefixed with an ampersand. That parameter will receive the block as a Proc object.

 def meth1(p1, p2, &block)
   puts block.inspect
 end
 meth1(1,2) { "a block" }
 meth1(3,4)

produces

 #<Proc:0x028e75f8@block3.rb:4>
 nil

2. By calling Proc.new, again associating it with a block.

block = Proc.new { "a block" }
block → #<Proc:0x028e77f0@block4.rb:1>

3. By calling the method Kernel.lambda (or the equivalent, mildly deprecated method Kernel.proc), associating a block with the call.

block = lambda { "a block" }
block → #<Proc:0x028e77f0@block5.rb:1>

The first two Proc objects are identical in use. These objects can be referred to as raw procs. The objects generated by the lambda keyword adds some additional functionality, though.

break, return, and next

Executing next within a block causes it to exit. The value resulting from the block invocation is the values passed to next, or nil if no such values are passed.

Within a block, break and return have mostly the same semantics. In a raw proc they will terminate the scope that yielded to the block, provided that this scope is still valid. Otherwise either a "break from proc-closure" or "unexpected return" LocalJumpError will be raised.

Within a block created by Kernel.lambda or Kernel.proc, a break or return will simply return from the block with the values provided.

For this reason, if you should use Module#define_method, you should pass it a proc created using lambda instead of Proc.new, to get natural return-semantics.

Block parameters

The parameters to blocks works differently based on which kind of naming is used. There are several ways to set variables outside the scope of the block by using different parameter names:

$var1 = "foo"
class A
  def initialize
    @var2 = "bar"
  end
  def test_block
    var3 = []
    var4 = {}
    1.upto(1) {|var0| } #local variable established
    1.upto(1) {|$var1| } #sets global variable
    puts $var1
    1.upto(1) {|@var2| } #sets instance variable
    puts @var2
    1.upto(1) {|var3[0]| } #sets array value
    p var3
    1.upto(1) {|var4[:test]| } #sets hash value
    p var4
    1.upto(1) {|self.var5| } #calls method var5=
  end
  def var5=(val)
    puts "setting var5 to #{val}"
  end
end
  
A.new.test_block

This will produce:

1
1
[1]
{:test=>1}
setting var5 to 1 

These versions aren't that useful since you can accomplish the same thing with an explicit assignment:

$val = 0
1.upto(1) {|v| $val = v }

and this is much more readable.

Another reason for not using these exotic versions of parameters passing is that not all Ruby implementations support them at present, and Ruby 1.9/2.0 will support only using local variables as block parameters.

Module_eval and blocks

One thing to be careful with is the scoping rules when establishing constants. There are some situations where module_eval behaves differently depending on if it's given a block or a string:

class A
  FOO = "baz"
end

class B
end

B.module_eval do
  FOO = "bar"
end

B.module_eval("BAS = 'bar'")

p A.constants
p B.constants

this results in the somewhat counterintuitive output

["FOO"]
["BAS"]

since in the first B.module_eval, the FOO-constant is created at load-time, in the scope where the block is created, which is at top-level.

Note: this behavior is due to Ruby 1.8 determining constant scoping at parse/compile time. Ruby 2.0 is supposed to fix this, so that the constants above would be scoped within the modules in which they are evaluated.

Personal tools