TL; DR Get to know Ruby’s Enumerator class. (Note that it’s distinct from the more commonly known Enumerable module, which it includes.) There are lots of great blog posts about Enumerator out there by people more knowledgeable than I; go read them sometime. This one focuses on using Enumerator.new as a more expressive alternative to recursive functions, with an emphasis on practical tasks rather than boring shit with numbers.

Disclaimer: YMMV. Take this with a grain of salt. A giant grain of Himalayan Pink Salt. And while you’re at Whole Foods, pick me up some quinoa chips and the new Sufjan Stevens album on vinyl.

Talking about recursive function makes you look smart

I am the first to admit that it feels great sitting in your favorite coffee shop, chatting on the phone with other remote developers, expounding on your new recursive function in needless detail, fretting over manufactured pros and cons, part of you believing that strangers at nearby tables silently admire your intellect, all the while wondering if your espresso isn’t quite as frothy as yesterday’s, or if that’s just in your head. Yes, all of that is almost certainly just in your head.

But writing them doesn’t make you feel smart

Picture your best recursive Fibonacci method. You have to set a stop point. The caller is blocked until the entire sequence is generated and returned. Now you have an enormous array of large integers sitting in memory. To improve speed you probably found some clever way to maintain state through the call chain. The line separating your algorithm from the busy-work of state handing became blurred. At some far-off but very real point you know you’ll see SystemStackError: stack level too deep. But don’t feel like a dullard; there is hope.

Using Enumerator (or generators in general) makes you look and feel smart

Here’s a Fibonacci sequence generator (shamelessly lifted from Ruby’s docs) that naturally avoids the problems endemic to the recursive approach:

fib = Enumerator.new do |y|
  a = b = 1
  while true
    y << a
    a, b = b, a + b
  end
end

# Print each number in the sequence *as it's calculated*. It will never stop running.
fib.each do |n|
  puts n
end

# Stop after 100 billion
fib.each do |n|
  break if n > 100_000_000_000
  puts n
end

# Get the first 100
a_hundred_fibs = fib.take(100)

# Find the first Fib number greater than 9 billion
result = fib.detect { |n| n > 9_000_000_000 }

# Now find it along with it's position
result, idx = fib.each_with_index.detect do |n, i|
  n > 9_000_000_000
end

That doesn’t scratch the surface of Enumerator. But it’s enough for our purposes. Enumerator is all about “lazy evaluation”, meaning that each element is generated only as it’s needed, and only those elements that are needed are generated.

Hey, you said no boring shit with numbers!

Very well then. Let’s use Enumerable to send a fax and then write the worst game ever!

Sending faxes

Yes, sending faxes via HTTP is a thing. But let’s say more generally, “You need to trigger some remote event, check on it’s status, and continue retrying until it succeeds.” You could write it one big recursive mess. Or you could imagine that each fax attempt is an iteration out of an unknown total.

require_relative 'libfax'

puts "Sending fax..."
attempts = fax('555-555-5555', "Here's a fax!")
attempts.each_with_index do |status, i|
  case status
  when :sent
    puts "Fax sent!"
    break
  when :busy
    puts "Line busy!"
    if i < 10
      sleep 2
      next
    else
      puts "Tried 10 times; aborting."
      break
    end
  else
    puts "Error!"
    break
  end
end

Gists: fax.rb libfax.rb

As I hope you’ll see, using Enumerator allows for extremely fine-grained separation of concerns. attempts is a black box iterator. All business logic is completely handled by the caller. All you need to know is that each iteration attempts to send the fax. The status is returned to you, and you decide whether to continue to the next attempt or to break.

Is there a game more boring than 20 Questions? How about Infinite Questions?

require_relative 'libguess'

puts "Let's plan Infinite Questions!"
turns = guessing_game
turns.each do
  print "Guess the word: "
  gets.chomp
end
puts "You win!"

puts "Let's play 20 Questions!"
turns = guessing_game
won = turns.each_with_index do |_,i|
  break if i+1 > 20
  print "Guess the word: "
  gets.chomp
end
puts won ? "You won!" : "You lose :("

Gists: guess.rb libguess.rb

Another block box iterator offering complete control to the caller. You can use various Enumerator and Enumerable methods to completely change the rules. There’s one interesting twist with this black box, though. It listens to the response from your block. That’s how it knows what you guessed. And it returns true when you win.

It’s over!

Check out the documentation of Enumerator class to learn more. And while you don’t have to memorize the Enumerable module, you can never be too familiar with it. And have you ever read through all the methods in Array? There’s some awesome built-in functionality there!