Ruby Blocks and Arguments

At Bloc, we encourage many of our non-technical teammates to learn how to program and like to help them along the way.

One of my coworkers who is brand new to Ruby was trying to evaluate the following code and was getting an error:

words  = []  
words.each print {|words|}  
# => ArgumentError: wrong number of arguments (1 for 0)

What was going wrong here? This seemingly cryptic error message revealed a great opportunity to explain the difference between blocks and arguments.

I thought I'd come up with a simple example to try to explain the difference from a beginner perspective.
At a high level, ruby methods can take two types of input: blocks and arguments. An argument is a way to provide a value (or an object that contains values) to a method.

def how_many_cats?(number)  
   puts "We currently have #{number} cats in a box!"
end  

In this example, number is an argument. Once we define the how_many_cats? method , we can run the code following code and get the output (after the # =>):

how_many_cats?(7)  
# => We currently have 7 cats in a box!

Blocks, on the other hand, are a way to provide additional instructions to the method using values that are available to the method. This may sound a bit confusing, so let's peek at an example. For this example, we're going to modify our how_many_cats? method so it can implement instructions provided in a block.

def how_many_cats?(number)  
   puts "We currently have #{number} in a box!"

   # We're modifying our method here by adding the yield keyword 
   # `yield` signals to our method to perform the instructions 
   # sent in by the block
   yield
end  

In this updated method, we now have enabled the ability to act on a block that is passed to the method. The method is told to act based on the instructions in the block using the yield keyword.

So, let's look at an example.

# the block is everything between the curly brackets
how_many_cats?(7) { puts "GAH! One cat escaped!" } 

# => "We currently have 7 cats in a box!"
# => "GAH! One cat escaped!"

You'll notice that the now the how_many_cats? method can take new instructions from the block and perform them at the place that the method calls yield.

We can also write the block using the do and end keywords:

# the block is everything between the `do` and `end`
how_many_cats?(7) do  
  puts "GAH! One cat escaped!" 
end

# => "We currently have 7 cats in a box!"
# => "GAH! One cat escaped!"

Pretty nifty, huh? It gets cooler! We can even get access to the the argument that we provided to how_many_cats? by passing yield an argument of its own.

def how_many_cats?(number)  
   puts "We currently have #{number} in a box!"

   # yield now accepts the argument: number
   yield(number)
end  

Now, when we call how_many_cats?, we can access the original argument we passed the method. We do this by adding an argument inside the block that is enclosed by two |s (e.g. |num| in the next example).

how_many_cats?(7) do |num|  
  puts "GAH! One cat escaped!" 
  puts "Now we've got #{num - 1} cats in a box!"
end

# => "We currently have 7 cats in a box!"
# => "GAH! One cat escaped!"
# => "Now we've got 6 cats in a box!"

Now that we've explored the differences between blocks and arguments, let's try to tie this back to the issue that my coworker was having. Just to refresh, my coworker was getting an error from running the following code:

words  = ['jimmy', 'johnny']  
words.each print {|word|} # => ArgumentError: wrong number of arguments (1 for 0)  

So why was my coworker receiving this error? Well, one thing this error message is telling us is that the each method is expecting 0 arguments, but received 1. In particular, it received the print keyword as an argument**.

Since each is expecting only a block, we need to revise the code so that the instructions that we want to provide the each method (i.e. print each word in our words Array) lives inside the block.

words  = ['jimmy', 'johnny']  
words.each {|word| print word } 

# => jimmy
# => johnny

Hopefully this distinction is now clear. Happy hacking!

** Ruby Quirk - When Ruby encounters a method name, a space, and then a new value, it assumes that the value is an argument for the method. In other words, how_many_cats? 7 is interpreted the same as how_many_cats?(7). This is an example of what programmers call "syntatic sugar", or a superficial addition to a language that makes our code easier to read.