Ruby: Yielding, Enumerables, and You

When I started learning Ruby, I started noticing code snippets like the following:
name_array.map { |name| name.upcase }
or:
name_array.find_all do |name|
name.length > 5
end
What are those little pipes for? Where did the word inside the pipes come from? What’s that second part inside the curly braces or do-end statement? It kind of looks like a conditional, but I don’t see any if-else statements so it’s just sitting there on its own? What is going on here???

Okay, so I quickly found out that those are examples of enumerables — one of Ruby’s handiest features, and worth an entire separate discussion. But underneath the hood, what’s really at play is the yield keyword: deceptively simple in appearance, but very powerful, and possibly even handier than enumerables — after all, without yield, enumerables wouldn’t even exist.

So what is yield?
To understand yield, let’s look at an example first:
def interrupting_cow
puts "Please let me talk without interrupting just for once."
yield
puts "I don't know what I expected, you never listen."
endinterrupting_cow do
puts "MOO"
end
Running that code produces the following:

Let’s break it down:
First, we define a function interrupting_cow
. It puts
a statement to the terminal. Then, it uses that mysterious yield
keyword. Finally, it puts
out another statement.
Once the function is defined, we then actually call on it, using a do-end statement that puts
out our iconic interrupting cow phrase. This do-end chunk (or { }
if you’re writing a one-liner) is called a block.
The output of the function call shows us what’s going on with yield
: The function runs normally and puts
the first phrase. Then, when it hits yield
, it jumps outside of the function and runs the code inside the block of the call (our interrupting cow says “MOO”). Once that code is done running, we jump back into the function and finish running the code there. So that’s yield
in a nutshell — “Keep running until you hit me, but then stop and run all the code inside the block; once that’s done you can finish the function where you left off.”
Optional blocks
So now we’ve seen a very basic yield, but what if our ill-behaved cow is slowly learning some manners and only interrupts sometimes? Let’s try running the code below so she doesn’t say “MOO” while the farmer’s trying to talk:
def maybe_interrupting_cow
puts "Please let me talk without interrupting just for once."
yield
puts "Thank you for listening politely!"
endmaybe_interrupting_cow
Uh-oh! Trying to run that produces the following error message:

The program puts
out the first statement, but when we hit the yield
, we get an error. By default, yield
expects to be given a block, and errors out if that doesn’t happen. Fortunately, we can use the block_given?
method to check on that. Let’s code a little logic into the conversation between our cow and farmer, then call the function once with a block and once without:
def maybe_interrupting_cow
puts "Please let me talk without interrupting just for once."
if block_given?
yield
puts "I don't know what I expected, you never listen."
else
puts "..."
puts "Thank you for listening politely!"
end
endmaybe_interrupting_cow do
puts "MOO"
endprint "\n"maybe_interrupting_cow

Excellent! Our farmer can now react appropriately based on Bessie’s manners.
Yielding with parameters
We’ve seen how yield
works at a basic level, and how to check if a block is given, but there’s one piece we’re missing that makes yielding so powerful: arguments. You can pass an argument to yield
inside the method definition, and when you actually call the function, yield
will in turn pass anything in your argument into the block — whatever the block happens to be. Blocks take block arguments, wrapped in braces | |
, and can be named anything since they’re “anonymous,” or passed in from outside the block. If that sounds confusing, let’s look at a code example and its output:
def cow_noise
yield "moo"
endcow_noise do |noise|
puts "The cow says #{noise}."
endprint "\n"cow_noise do |noise|
puts "A duck does not say #{noise}."
end

In the method definition, we’ve passed yield
an argument of the string “moo.” In the method calls, we include a block argument called noise
. We’ve given it that name, but it could be called anything (just be descriptive with your block arguments). We tell the method calls to puts
a string and interpolate the block argument. But remember, the block argument noise
is anonymous — It doesn’t do anything on its own, but rather gets the argument passed to yield
in the method definition, and hands that off into the body of the block.
Okay… so what’s the point of all this?
If the code examples above seem abstract and maybe a little useless, well… it’s kind of because they are. That’s the trick with a lot of coding concept examples; they show what a feature does but not necessarily how it’s used. Let’s try and tie this together by showing the way yield
functions under the hood of enumerables, that powerful Ruby feature we mentioned at the top.
All enumerables are built on the back of the each
function. As a reminder, each
looks like this:
an_array.each do |item|
# some stuff to do to each item of the array
end
You call each
on an array* with dot notation, give a descriptive name for an individual member of that array in the block argument, and then each of those individual members is passed into the block to be manipulated by whatever code you’ve put there. each
just works out of the box with Ruby, but what if we wanted to write our own version? Look at the code below with a comment above each line to explain what it’s doing.
* Yes, you can call
each
on non-arrays too, but for the purposes of demonstration we’ll limit this discussion to arrays only.
def custom_each(array)
# check that user is using a block when calling method
if block_given?
# initialize counter
i = 0
#iterate over every item in the array
while i < array.length
# whatever is in the block, do that to each successive array element
yield(array[i])
# increment counter
i += 1
end
# return original array
array
end
end
By using a counter and ‘while’ loop, we can harness the power of yield
to pass every member of a collection into a user-defined block:
farm_animals = ["cow", "pig", "goat", "duck", "horse"]farm_animals.each do |animal|
puts animal
endprint "\n"custom_each(farm_animals) do |animal|
puts animal
end

Beautiful!
Yield, each, and more advanced enumerables
So each
is pretty cool, but it’s 1) very generic, and 2) has a return value of the original array. It’s versatile, but not specialized. But Ruby gives us a whole module of enumerables to use, each with a particular use case, and each leveraging the power of each
and yield
under the hood. There’s a ton of them and it would take forever to look at each one, but let’s close this article out by taking a look at custom versions of the two all-star enumerables mentioned at the top: map
(aka collect
) and find_all
(aka select
).
We use map
to make some change to every element of an array and return that changed array:
def custom_map(array)
# check that user is using a block when calling method
if block_given?
#initialize new array for eventual return
new_array = []
# whatever is in the block, do that to each successive array element and push it onto the new array
array.each do |item|
new_array << yield(item)
end
# return new array
new_array
end
end
We use find_all
to go over every element of an array, returning a new array of only members meeting some criteria we define in the block:
def custom_find_all(array)
# check that user is using a block when calling method
if block_given?
# initialize new array for eventual return
new_array = []
# look at every item in array
array.each do |item|
# only add item to return array if it evaluates 'true' against block condition
if yield(item)
new_array << item
end
end
# return new array
new_array
end
end
Though you may not be using yield
often in your own Ruby code, be aware that it’s still ever-present, hiding under the surface of every enumerable call. Hopefully these examples illustrate what a handy little tool it is, and maybe even inspire you to find some uses for it in your own custom methods.