Block Pattern #1: Enumeration

You may have fallen in love with Ruby because of the way it does enumeration:

 >>​ ​%w(look ma no for loops)​.each ​do​ |x|
 >>​ puts x
 >>​ ​end
 look
 ma
 no
 for
 loops
 =>​ [​"look"​, ​"ma"​, ​"no"​, ​"for"​, ​"loops"​]

Besides being very expressive, enumeration using blocks is more concise and less error-prone. It is concise because the block captures exactly what we want to do with each element (printing it out to the console). It is less error-prone compared to traditional for loops because it does away with indices that are prone to the infamous off-by-one error.

You should be familiar with this way of iterating over a collection, such as an Array. What is interesting is how these methods are implemented under the hood.

Going through the process of building your own implementation will give you a much deeper understanding of how methods and blocks work.

Implementing Fixnum#times

While it’s not surprising that Ruby is an object-oriented language, the extent of “object-orientedness” often surprises newcomers to Ruby. For example, most wouldn’t associate a number with the notion of an object. However, Ruby begs to differ by making code like this possible:

 >>​ 3.times { puts ​"D'oh!"​ }
 D'oh!
 D'oh!
 D'oh!
 =>​ 3

How is this possible? The answer is two-fold. First, 3 is an object of the Fixnum class. Second, the Fixnum#times method is what makes the preceding code possible.

What can we say about the Fixnum#times method? Well, it executes the block exactly three times. This information is taken from the instance of the Fixnum, 3. This detail is important, as you will soon see.

What can we say about the parameters of the block? Well, not much, since the block doesn’t take any parameters. Let’s implement Fixnum#times. Additionally, we will assume that each doesn’t exist.

Create a file called fixnum_times.rb. Fill in an initial implementation like so:

 class​ Fixnum
 def​ times
  puts ​"This does nothing yet!"
 end
 end

Thanks to Ruby’s open classes, we have now just overridden the default version of Fixnum#times and replaced it with our own (currently non-working) one. Load the file in irb using the following command:

 $ ​​irb​​ ​​-r​​ ​​./fixnum_times.rb

Let’s try this out:

 >>​ 3.times { puts ​"D'oh!"​ }
 puts "This does nothing yet!"
 =>​ ​nil

For now, nothing happens since we have overridden the default Fixnum#times method with our empty implementation. Remember that we imposed the constraint that we cannot use Array#each? The reason is that would make things too easy for us. We can fall back to a while loop:

 class​ Fixnum
 def​ times
  x = 0
 while​ x < self
  x += 1
 yield
 end
  self
 end
 end

Now, redo the steps with the updated code:

 % irb -r ./fixnum_times.rb
 >>​ 3.times { puts ​"D'oh!"​ }
 D'oh!
 D'oh!
 D'oh!
 =>​ 3

Again, self is the Fixnum instance, also known as 3 in our example. In other words, it is using the value of the number to perform the same number of iterations. Pretty nifty, if you ask me.

The most important part of the code here is yield. In this example, yield is called without any arguments, which is exactly what the original implementation expects. The return value of the times method is the number itself, hence self is returned at the end of the method.

Let’s keep up the momentum and implement Array#each.

Implementing Array#each

Take a close look at how the Array#each method is invoked:

 >>​ ​%w(look ma no for loops)​.each ​do​ |x|
 >>​ puts x
 >>​ ​end
 look
 ma
 no
 for
 loops
 =>​ [​"look"​, ​"ma"​, ​"no"​, ​"for"​, ​"loops"​]

The block accepts one argument. Create a new file, array_each.rb. As with the previous example, fill it with an empty implementation of the method that we are going to override:

 class​ Array
 def​ each
 end
 end
 
 %w(look ma no for loops)​.each ​do​ |x|
  puts x
 end

Let’s test that this implementation does override the default behavior:

 $ ​​ruby​​ ​​array_each.rb
 # Returns nothing

Since you don’t have the help of Array#each (that’s the whole point of this exercise), the iteration needs to be tracked manually. Once again, it’s time to reach for the humble while loop:

 class​ Array
 def​ each
  x = 0
 while​ x < self.length
 yield​ self[x]
  x += 1
 end
 end
 end
 
 %w(look ma no for loops)​.each ​do​ |x|
  puts x
 end

Now when you run array_each.rb, the results get printed as expected:

 $ ​​ruby​​ ​​array_each.rb
 look
 ma
 no
 for
 loops

Notice how self is being used here. First, the while loop uses self.length to determine if it should continue looping or break out of the loop. Second, the individual elements of the array are accessed via self[x]. The value of this is passed into the supplied block, which, in our example, simply prints the elements out.

Blocks can do more than enumeration. In fact, one common use case of blocks is to handle resource management. Let’s explore how.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.216.96.94