Last Updated:

How to Deal with Blocks

When I switched to Ruby from the PHP background, one of the most gray areas for me was blocks. In Ruby, there are blocks everywhere, and their poor understanding will stop your learning at the "beginner" stage. When you're just starting out with Ruby, you'll inevitably use blocks solely for iteration.

Hi Blocks

3.times {|n| puts "Printed #{n} times" }

The above code should be "hello world" blocks, and from this single line we learn a lot about Ruby. We have a number (Fixnumtimes In case you missed an important bit, "the method that takes the block." At the moment, just blindly accepting a block is just a piece of code that we can pass, and which can only be defined after the method is called, it can take parameters and has 2 styles of syntax. We have blocks defined in curly braces. As stated above, or defined in doend , it is common practice to use curly braces if the contents of a block consist of a single line (although Avdi Grimm gives a good argument for mixing them depending on what the block does).

3.times do |n|
  puts "Printed #{n} times"
end

In PHP, we're not used to blocks at all. In fact, it could be said that for something so trivial, it would be much better to just use our reliable cycle (for everyone). But too many times I've come across the following.

for($i = 0; $i < count($values); $i++)

Now there are a lot of problems in this single line of code (now it is used next to you). For starters, count() But even that's not my biggest problem. Using the variable $i$i I blame C $i

If we consider past scope, semantics, or even implement it as a foreach loop, we still have what looks like an external cyclic construct interfering with our data. I wrote such things day in and day out. But these days, after spending some time in real-time Ruby, it just can't use cyclic constructs like for Putting a business in a block, it gets cleaner, and we get closure for free (local block variables are only in the block's scope).

Closing PHP

In PHP 5.3, closures were introduced. It did the surgery we expect from times.

class MyInt {
  protected $value = 0;

  public function __construct($value) {
    $this->value = $value;
  }

  public function times($pBlock) {
    for($i = 0; $i < $this->value; $i++) {
      $pBlock($i);
    }
  }
}

$echo_statement = function($value) {
  echo $value;
};

$val = new MyInt(3);
$val->times($echo_statement);

So we basically created a new class that would act like Ruby Fixnum$i That's pretty powerful stuff, but look at the code loops we have to go through to get something as concise as a Ruby block.

Spill beans on methods

If we created our own times implementation

class Fixnum
  def times
    for i in (1..self)
      yield i
    end
  end
end

5.times { |n| puts "Im Hi #{n}" }

Above we rediscovered the Fixnumtimes class so we wrote a for loop that iterates through the range from 1 to whatever. The most interesting line in the above: yield i yield method

Wait a minute. Where did we pass by that block? Well, each method takes a block (whether you've identified it or not). So we don't even have to declare the block as a parameter for our times method He knows the block might be there, but we decided to let it work using yield.

So what happens if we forget to hand over a block? Well, the answer would be LocalJumpError: no block given However we can give this method a little more flexibility using block_given? .

class Fixnum
  def times
    if block_given?
      for i in (1..self)
        yield i
      end
    else
      Enumerable::Enumerator.new self
    end
  end
end

5.times { |n| puts "Im Hi #{n}" }
Im Hi 0
Im Hi 1
Im Hi 2
Im Hi 3
Im Hi 4
=> 1..5

5.times
=> #<Enumerable::Enumerator:0xb737acb4>

Here we added a bit of a curvilinear ball, returning a new Enumerable::Enumerator object This was mainly to mimic the real implementation times It will return an Enumerator object that will allow us to do the following:

enumerator = 5.times
=> #<Enumerable::Enumerator:0xb737acb4>
enumerator.max
=> 4
enumerator.min
=> 0
enumerator.minmax
=> [0, 4]

Block Sphere

Blocks are closures, they take a piece of code and its local environment (variables, etc.) and save it for execution later. Therefore, it is better to talk about the impact of blocks on the environment. According to what we naturally expect, variables defined within a block are discarded after the block is executed. But there are some unpredictable behaviors depending on which version of Ruby we use when defining blocks:

n = 10
y = 0
[1,2,3].each do |n|
  x = n
  y = y + n
end

y.inspect
# => "6"
n.inspect
# => "3" -- In 1.9.X this will be 10
defined? x
# => nil

As expected, the variable x However we can see n I don't know about you, but I feel that this behavior is unnatural. I've heard cases where this is really a "feature", but I have not yet had to deal with such situations. Finally, y

I mention this behavior with block parameters (n varies) for those of you who play with Ruby 1.8.x. In Ruby 1.9.x, the n parameter remains unchanged for the block. I prefer this behavior and suggest you also work with the latest and best version of Ruby.

Completion

  • Blocks are a piece of code that is later set aside for execution (go ahead, just say "close")
  • All methods in Ruby accept a block by default, and we can call the block using yield
  • Blocks work in their own closed environment
  • Variables that are local to the block are discarded after execution
  • Variables defined before the block is defined are enclosed in the scope of the blocks.

It's safe to say that we've made good progress on the way to exploring blocks, next time we'll look at the magic of lamdaProc.