Getting to Grips with Blocks II
In the last article we had a whistle stop tour of blocks. How they are defined, what purposes they serve and the differences between various Ruby versions. It was all very academic. But practically what benefit do we actually get from blocks. What makes them such a powerful tool for Ruby developers.
Procs and Lambdas
Before we get into some useful examples of blocks, we have to discuss Proc
and lambda
. These two allow you to define a block for later use using the call
method.
These “callable objects” are intended for use down the line in our applications execution. To drive the point home we need an example. Anything to do with money springs to mind, exchange rates, rates for different clients, discounts and so on.
def discount( rate )
Proc.new { |n| n - ( n * rate ) }
end
ten_percent_off = discount(0.1)
twenty_percent_off = discount(0.2)
ten_percent_off.call(100) # => 90.0
twenty_percent_off.call(60) # => 48.0
In the above example, we define a method discount
that accepts a rate and returns a Proc
object. No code is executed (well nothing is returned) all we have done is set up a little environment that takes a percentage off). We setup up two discounts and call them on the values 100 and 60 using call
. Just so you know now the discount
method would work just as well written using a lambda
:
def discount( rate )
lambda { |n| n - ( n * rate ) }
end
Why Two Ways to Define a Proc?
Proc
and lambda
have subtle differences. From the previous example we see the block accepts a parameter n
, the lambda
is pretty strict about these parameters, if we called it incorrectly like so:
ten_percent_off.call(10, "This should not be here")
# => ArgumentError: wrong number of arguments (2 for 1)
The Proc
doesnt mind this sort of thing, it simply ignores the additional parameters. Similarly the lambda
will complain if we do not pass enough parameters, the Proc
again doesn’t mind and just assigns the excess parameters to nil
. It sounds like Proc
is a nice and more flexible way of doing things, however I rarely use Proc.new
it’s almost always lambda
, why? Well, 90% of the time I like airity (number of parameters) to be enforced and a horrible error thrown if I do something wrong. If that reason seems weak, then we can consider how Proc
and lambda
handle return
.
def hello_me
hiya = Proc.new { return "Hiya" }
hiya.call
puts "Woe is me, I will never fulfill my purpose"
end
hello_me
#=> Hiya
# Using a lambda
def hello_me
hiya = lambda { return "Hiya" }
hiya.call
puts "Finally I fell like I have contributed"
end
hello_me
Finally I fell like I have contributed
As seen the Proc
returns from the scope in which it was called, and the lambda
returns from the block, allowing our final message line to fulfill its purpose. I am not saying “pick one, and never use the other”, far from it. Knowing the implications of both simply allows us to use the correct definition when appropriate. Still I do use lambda
a heck of a lot more.
Practical Blocks
So now we want to look at how we can use these blocks in a practical way that is more than just iteration that we are used to.
Simplifying interfaces
The first thing that springs to mind is a technique I use on factory methods. A factory being a piece of code that returns an object based on it’s inputs. A good example would be producing text for a label print.
class AddressLabel
def initialize
@lines = []
end
def self.generate &block
label = AddressLabel.new
label.instance_eval(&block)
label.print
end
def print
@lines.each do |line|
puts line
end
end
def address(lines)
lines.each do |line|
@lines << line
end
end
end
AddressLabel.generate do
address ["Lets Be Avenue", "Glasgow"]
end
# Another way of creating the same label would be ...
label = AddressLabel.new
label.address ["Lets Be Avenue", "Glasgow"]
label.print
Here the instance_eval
evaluates the block within an instance of AddressLabel
. Both methods of producing the label do the same thing only using the block format is way sexier as well as making it more natural to read. No doubt you will have seen this type of interface before in many of the gems available.
Setup and Tear Down
Another common use for blocks is pre and post processing. The most common example on the web is probably Ruby’s standard library for File.open
File.open('testfile', 'w') do |f|
f.write 'Some text'
end
file = File.open('testfile2', 'w')
file.write 'More text'
file.close
The second example is probably what you will be used to with PHP, but we can see the nice block implementation takes care of closing the file behind us. You can apply this kind of implementation anywhere tear down actions are involved. I remember writing some php code that created PDF files. The code was something like
try {
$pdf = new PDFlib();
if ($pdf->begin_document("file_name", "") == 0) {
die("Error: " . $pdf->get_errmsg());
}
$pdf->show(“hello world”);
$pdf->end_page_ext(“”);
$pdf->end_document(“”);
} catch (Exception $e) {
exit();
}
This simply opens a new file writes some text and closes it again, there is some very basic exception handling to exit the script if things go awry. It was never meant to be the most elegant piece of code. However as we have such elegant examples of interfaces in the Ruby standard library, writing such code would feel just plain old “dirty” in Ruby.
It could be cleared up a lot in PHP (even more so with PHP anonymous functions) but the tools at our disposal pre PHP5.3 were just not up to the job of extracting all this open document / close document chores without wrapping up the PDFlib library.
So what about setup? Everything we just discussed was post processing so what benefit do blocks provide on setup. When creating methods that start to use a lot of conditional logic to determine the outcome I look at passing it blocks.
def more_than_ten_rows? rows
if rows.length > 10
yield rows
end
end
more_than_ten_rows? (1..10).to_a do |row|
puts row
end
So with this simple piece of code more_than_ten_rows?
acts as a gate, allowing the block to run when we have more than the required number of rows. The alternatives would be to move the conditional logic to the presentation, or the presentation to the logic, using the block we have achieved some basic separation.
Wrapping up
I think it’s fair to say unless you get a good grip on blocks when learning Ruby you will never really fulfil your potential. It’s common to hear “blocks are everywhere” and it’s true, so you will be missing the party if you don’t get some quality time with them.
They are there to help us developers trivially fulfill code in a succinct clean way, using them will become more natural over time and once you get to that stage you will wonder how you got by without them.
It’s definitely worth using the PHP equivalent, I never used them (even though I had 5.3 on the production environment) until I came to writing this article, the syntax is more verbose than Ruby, but, hey, its a pretty decent trade off and I fully believe that understanding this relatively new addition in PHP will help you grasp a core fundamental in Ruby.