In Progress
Unit 1, Lesson 21
In Progress

Next

Video transcript & code

In the last episode, #259, I made passing mention of the Ruby next keyword. It occurred to me that not everyone might be familiar with this keyword. Just like redo, it's one of those features where you can spend years writing perfectly good code without knowing about it. But once you do know about it, you can use it to save some effort or make certain idioms more expressive. And even if you use next every day, in the episode after this I'll be covering some advanced uses that might just teach you something new.

At its most basic, next works like the continue loop control keyword found in languages like Java or C. For instance, let's say we have a list of objects we need to say goodnight to. Unfortunately, some blank strings have snuck into the list. As a result, our output is messed up.

objects = ["house", "mouse", "", "mush", "",
           "little old lady whispering 'hush'"]

objects.each do |o|
  puts "Goodnight, #{o}"
end

# >> Goodnight, house
# >> Goodnight, mouse
# >> Goodnight,
# >> Goodnight, mush
# >> Goodnight,
# >> Goodnight, little old lady whispering 'hush'

Let's assume that we can't simply remove the blank strings from the list. One way to deal with them would be to put a conditional statement around the puts line. It will only execute the puts if the string is non-empty.

objects = ["house", "mouse", "", "mush", "",
           "little old lady whispering 'hush'"]

objects.each do |o|
  unless o.empty?
    puts "Goodnight, #{o}"
  end
end

# >> Goodnight, house
# >> Goodnight, mouse
# >> Goodnight, mush
# >> Goodnight, little old lady whispering 'hush'

This works fine. But this style of code always bothers me a little bit. By pushing the actual "meat" of the block down inside a guarding conditional, we've given this junk-handling code greater prominence than I feel it deserves.

If this were a method we were writing, I would prefer to write it like this, with a guard clause which returns early if the input is garbage. Once we are past the guard clause, we can mentally dispense with that case entirely. There is no lingering context to keep us thinking about it.

def say_goodnight_to(object)
  return if object.empty?
  puts "Goodnight, #{object}"
end

Obviously, we can't use return inside an #each loop, because that would abort the iteration and force the whole method to return. Instead, we can use the next keyword to tell Ruby to skip forward to the next iteration.

objects = ["house", "mouse", "", "mush", "",
           "little old lady whispering 'hush'"]

objects.each do |o|
  next if o.empty?
  puts "Goodnight, #{o}"
end

# >> Goodnight, house
# >> Goodnight, mouse
# >> Goodnight, mush
# >> Goodnight, little old lady whispering 'hush'

When we run this version, it leaves out the empty entries in the list. In effect, next gives us a way to add guard clauses for blocks.

You might recall that back in episodes #70 and #71 we introduced the break keyword. If you haven't used the break and next keywords much, you might be a little unclear in how they differ from each other. In order to clarify that difference, let's change our next to a break and run the code again.

objects = ["house", "mouse", "", "mush", "",
           "little old lady whispering 'hush'"]

objects.each do |o|
  break if o.empty?
  puts "Goodnight, #{o}"
end

# >> Goodnight, house
# >> Goodnight, mouse

This time, instead of just skipping the blank entries, the iteration over the array halted completely at the first blank string. That's the difference in a nutshell: next skips to the next iteration, whereas break breaks completely out of the method that the block has been passed into. In this case, that method is #each.

Thus far, we've been coding purely in imperative terms. We're iterating over the elements of an array, printing some of them to standard out, and ignoring any return values.

Let's switch that around now, and look at things from a functional perspective. We'll change the #each to a #map, and assign the result to a variable. For now, we'll get rid of the next. And we'll lose the puts and just return the constructed string from each block invocation.

objects = ["house", "mouse", "", "mush", "",
           "little old lady whispering 'hush'"]

result = objects.map do |o|
  "Goodnight, #{o}"
end

result
# => ["Goodnight, house",
#     "Goodnight, mouse",
#     "Goodnight, ",
#     "Goodnight, mush",
#     "Goodnight, ",
#     "Goodnight, little old lady whispering 'hush'"]

The result is an array of strings, including some junk strings. Let's see what happens when we reintroduce our guard clause using next.

objects = ["house", "mouse", "", "mush", "",
           "little old lady whispering 'hush'"]

result = objects.map do |o|
  next if o.empty?
  "Goodnight, #{o}"
end

result
# => ["Goodnight, house",
#     "Goodnight, mouse",
#     nil,
#     "Goodnight, mush",
#     nil,
#     "Goodnight, little old lady whispering 'hush'"]

This time, where the input data had a blank string, we now have nil values. We now know that the return value of a block invocation which is skipped by next is nil.

This is a handy behavior, since it means we can then use compact to filter out all the nil entries.

objects = ["house", "mouse", "", "mush", "",
           "little old lady whispering 'hush'"]

result = objects.map do |o|
  next if o.empty?
  "Goodnight, #{o}"
end

result.compact
# => ["Goodnight, house",
#     "Goodnight, mouse",
#     "Goodnight, mush",
#     "Goodnight, little old lady whispering 'hush'"]

We are not done. I have a lot more to show you about the next keyword. But in the interests of introducing just one idea at a time, I'm going to end for now and pick up the subject again in the next episode. If everything we've seen so far was old hat to you, be patient: the next installment will dig a lot deeper. Until then, happy hacking!

Responses