Everything You Need to Know About Blocks in Ruby

Eric Girouard
HackerNoon.com
7 min readMay 11, 2019

--

The complete guide to one of Ruby’s most powerful features

Blocks are amongst the most commonly used features in Ruby, and the majority of Ruby developers are blissfully unaware of their presence — including just how powerful they can be. Blocks are far from complicated, but they aren’t quite child’s play either.

Let’s begin with a classic example to expose the block for the hero it really is:

Here we call the method ‘times' on the Integer 5, and print “Hello Medium” with each iteration.

To anyone who has spent time coding in Ruby, this should look very familiar. do...end has a home in almost every Ruby script I’ve ever written. Although technically Ruby style is to use {} if the block can be condensed to a single line:

Behind the scenes, puts “Hello World” is a block, and is input as an anonymous parameter to the function times. If we lookup the API documentation for times, it will tell us just that! These docs also inform us that our block can accept an argument if we so desire:

5.times {|value| puts “Hello Medium #{value}” } would be the one-liner

Which will print the following to the console:

Another common use case of blocks is during file I/O. Whether you were aware of it or not, anytime you opened a file with IO.foreach, you actually passed in a block that got executed for each line of your file!

Blockception!

We’ve been using blocks this whole time and have been mostly unaware of their massive potential — let’s change that.

In Ruby, we ‘yield’ to blocks

Blocks are oft referred to as ‘anonymous methods’, as they can be passed implicitly into any method in Ruby. All it takes to execute — or call — a block is to yield to it:

“Hello Medium : 2019–04–28 08:23:45 -0400"

Here we pass a block containing a single string: “Hello Medium” to our method say_with_time. This method then yields to its block, and prints the current time along with it.

Theres some odd behavior with this method though…If we inspect it with pry:

Our dear block is nowhere to be found…

The first thing we’ll notice is that our block is not listed as a parameter in scope. We can yield to it, and check it’s boolean status with block_given?, but we cannot call it as a parameter.

Instead of relying on implicitly passing blocks, we can define them explicitly as an input parameter to our method. Exposing the block in this fashion gives us valuable insight into its origins:

block is now a local variable in scope, and after inspecting it we finally unravel the origins of our dear block, The Object: Proc.

Proc

Procs, short for Procedures, are essentially blocks encapsulated inside a Ruby Object. Procs supply our blocks with additional functionality and permit them to be passed around as normal objects can be.

Naively, we can use Procs the same way we utilized Blocks previously:

Hello Medium : 2019–04–28 09:30:03 -0400

Here we explicitly define a block and pass it into our say_with_time method, as compared to our first example where the block was passed implicitly. The benefits here are the same as saving primitives in variables: reuse. You wouldn’t hard-code “Hello Medium” everywhere you wanted it, you’d save it as a variable and use that instead, the same logic can be applied to Procs/blocks!

Procs have much more potential than this simple example. The limits to what a Proc can accomplish are only limited by your imagination and creativity. As I mentioned, you yield to a block by ‘calling’ it. In this sense, we’ve essentially just unveiled callback functions in Ruby!

At most one block may be passed into a method, directly or indirectly, however you can ship multiple Procs into a method if desired:

Lambdas

The term ‘Lambda Expressions’ is synonymous to ‘anonymous functions’ in programming and is derived from the model of calculus by the same name. Its technical definition is “A function defined, and possibly called, without being bound by an identifier”. While blocks and procs also fit this definition, lambdas have a few key differences that let them shine out in a crowd.

First, let’s explore lambda syntax in contrast to that of blocks and procs:

How to use each type of Ruby closure

Lambdas have a few key differences to procs, which can be quite beneficial depending on how you look at them.

  • Lambdas enforce the number of arguments you pass into them
  • Lambdas handle the return keyword differently than Procs

Lambda functions will throw an ArgumentError if you pass in the incorrect number of arguments, whereas Procs will simply fill in the gaps with nil.

For procs, return means “return from the surrounding method”, whereas for lambdas return means “return from the lambda”.

Here we see that Lambdas correctly govern their expected arguments, whereas Procs do not
Examples of how Procs and Lambdas handle the ‘return’ keyword

Here is the output of the above script:

Notice here that “Done running Proc!” on line 8 is never run, as the return inside our proc_return jumps out of the entire test_proc_returns method.

Passing and Executing Blocks

Since we’re almost block experts, it’s time to put our newfound skills to use. We’ve explored passing blocks implicitly to Ruby methods, now its time to for this post to get explicit. Using the powerful & symbol before a variable in our method header, we can convert any Proc/Lambda into a block!

‘&’ converts a Proc/Lambda to an explicit Block

In this example, we’ve begun spelunking into the use-cases of blocks as callback functions. print_each_value is essentially a method encapsulated in a lambda, which I chose over Proc because we want to ensure it has an argument to print. Without changing the print_blocks method, we can create new Lambdas to alter its functionality instead. The above snippet outputs the following:

This example is still naive, however now that we understand &, we can fully immerse ourselves in block-based callbacks.

As I said before, the possibilities here are only self-limited. Ruby’s mantra was never to ask ‘why?’, the functionality is provided to you, and it’s up to you to decide what to create with it. Here are a few interesting examples to get your mind going:

Try

A method that accepts a boolean, a variable argument array, and a block. Try then executes the block, passing in argument[0], and prys if an error occurs.

A clear example of how powerful blocks can be

On our first attempt, try already proves its worth:

I forgot to create the needed file…

After fixing that blunder, we can re-run our script:

I love this example because my shit breaks a lot, and being able to immediately identify why it's failing, and see said failure’s scope, allows me to solve my problems much faster. This example is easily extendible as *params can contain any number of arguments — just remember that our block ALWAYS has to be at the end of a parameter list.

Execute Around

Passing blocks into methods is common enough in Ruby that it has its own name: Execute Around (the block). In this programming model, we define a method that accepts a block and performs a series of tasks around it, such as printing, logging, accessing a database, or whatever your little coder heart desires.

Our Block has finally reached its potential

And the log output from log_1556468836.txt:

Quick Review

  • Blocks are anonymous functions implicitly passed into functions. They are instances of the Proc class that take their scope with them when passed into a method
  • Procs come in two forms: Proc and Lambda, which differ only in argument enforcement and their handling of the return keyword
  • Procs, Lambdas, and blocks are all examples of closures in Ruby
  • Procs/Lambdas can be converted to blocks with the & symbol
  • Blocks’ power comes into fruition when implemented as callback functions

--

--

Eric Girouard
HackerNoon.com

Boston based Software Engineer specializing in Hadoop and big-data development. I enjoy writing about that which I’m passionate about.