Why you should use Enumerators instead of recursive functions in Ruby
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:
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!
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.
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?
Gists: guess.rb libguess.rb
Another block box iterator offering complete control to the caller. You can use various
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.
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!