Introducing Lyfe: Yield in JavaScript

June 23, 2011

I've been a fan of using yield to create generators in Python for a long time, and when I was dragged into the world of C#, I was thrilled to see that it supports this pattern as well.

Generators with yield

The yield pattern is a great way to “generate stuff” to be iterated over, and do so in a clear and easily readable way. The basic conceptual idea is: You have a function that returns multiple times, and all those different return values are just a collection of values to iterate over.

Blog posts that include code always need contrived examples that have nothing to do with the real world, so let's use FizzBuzz:

Write a program that prints the numbers from 1 to 100. But for multiples of three print “Fizz” instead of the number and for the multiples of five print “Buzz”. For numbers which are multiples of both three and five print “FizzBuzz”.

In Python, a generator that yields FizzBuzz “numbers” might look like this:

def infiniteFizzBuzz():
    i = 1
    while True:
        word = ""
        if i % 3 == 0:
            word = "Fizz"
        if i % 5 == 0:
            word += "Buzz"

        if word:
            yield word
        else:
            yield i

        i += 1

To print the first hundred of them, we do this:

from itertools import islice

for x in islice(infiniteFizzBuzz(), 100):
    print x

In C#, the whole thing looks extremely similar. Note that we're taking the simple route of just yielding objects, since we can have both integers and strings as results; an enterprise version would of course have something like an IEnumerable<IFizzBuzz>. Well, the “actual” Enterprise FizzBuzz has ITransformer to turn numbers into strings. Anyway; I digress.

public static IEnumerable<object> InfiniteFizzBuzz()
{
    var i = 1;
    string word;
    while (true)
    {
        word = "";
        if (i % 3 == 0)
            word = "Fizz";
        if (i % 5 == 0)
            word += "Buzz";

        if (word.Length > 0)
            yield return word;
        else
            yield return i;

        i++;
    }
}

To again output one hundred, using a little bit of LINQ:

foreach (var x in InfiniteFizzBuzz().Take(100))
{
    Console.WriteLine(x);
}

So what about JavaScript?

Tough luck. There's no yield in JavaScript. Well, in Firefox there is, if you give your <script> tag a new type, namely "application/javascript;version=1.7". Chances are that sooner or later, yield-based generators will be part of the standard, but that's dreaming of the future for now.

Fully emulating this behavior in JavaScript in a way that all current browsers understand is not possible, because you don't have coroutines – there's no way to leave a function and later come back to continue where you left off.

But I really wanted to see yield in JavaScript, so I tried playing around with some callback trickery, and came up with “Lyfe”, which is an incredibly witty recursive acronym for Lyfe: yield for everyone.

Here's the above FizzBuzz example in JavaScript using Lyfe:

var infiniteFizzBuzz = Generator(function () {
    var i = 1, word;
    while (true) {
        word = "";
        if (i % 3 === 0)
            word = "Fizz";
        if (i % 5 === 0)
            word += "Buzz";

        if (word)
            this.yield(word);
        else
            this.yield(i);

        i++;
    }
});

And here is the corresponding iteration:

infiniteFizzBuzz.take(100).forEach(function (val) {
    console.log(val);
});

Watch out a little bit

Lyfe approaches the whole thing backwards: When encountering yield, instead of leaving the generator function to essentially go down a level in the call stack, instead yield is a function call that creates a new level on top of the stack. Interestingly, Eric Lippert identifies this method as a valid alternative in one of his blog posts on generators in C#.

It comes with a few drawbacks. For example, it's not possible to implement an iterator protocoll with this (think “next()”), and there are no syntax restrictions preventing you from, say, leaking yield out of the current scope. I may, at some point, add a way to prevent some weirdness at runtime, but currently, it's “just don't do it”.

The purpose of Lyfe (no pun intended) is not to make everything work identical to, say, Python; the purpose is to offer generator functions with yield, and a few tools to work with them.

The project is very new and experimental, but if you're interested, you can find it on BitBucket at bitbucket.org/balpha/lyfe. Be sure to tell me what you think!


previous post: A shout-out to the people of Meta Stack Overflow

next post: Look, honey! I injected a dependency!

blog comments powered by Disqus