Ruby: Yielding, Enumerables, and You

Image for post
Image for post

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???

Image for post
Image for post

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.

Image for post
Image for post

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."
end
interrupting_cow do
puts "MOO"
end

Running that code produces the following:

Image for post
Image for post

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!"
end
maybe_interrupting_cow

Uh-oh! Trying to run that produces the following error message:

Image for post
Image for post

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
end
maybe_interrupting_cow do
puts "MOO"
end
print "\n"maybe_interrupting_cow
Image for post
Image for post
(print “\n” in the code above is just to give us the line break in our output here)

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"
end
cow_noise do |noise|
puts "The cow says #{noise}."
end
print "\n"cow_noise do |noise|
puts "A duck does not say #{noise}."
end
Image for post
Image for post

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
end
print "\n"custom_each(farm_animals) do |animal|
puts animal
end
Image for post
Image for post
If you’re wondering why we call .each but custom_each(), it’s because #each is a method defined in the Enumerable module… which is a whole other discussion, so don’t worry about it for now.

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.

Former paralegal gladly opting for programming instead of law school. Engaged in a years-long, steady migration northward.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store