Dragon Ipsum

Modified June 17, 2016.

Generators and the spread operator

Generators and the spread operator in JavaScript are probably two of my favorite additions, just behind let, const, and arrow functions. It allows for richer functions to be constructed. Here’s an overview/tutorial of what it is, and how it can be used.

The beauty of generators

Generators, if you are not already familiar, are a special type of function that are able to sequentially yield the outputs of a function. The way JavaScript generators work is that there is a main generator object, from which you can create GeneratorFunction instances. You can obtain a single step of this instance using the next method, which returns an object containing two key-value pairs, value and done. The value key is what is yielded by the generator, and done is a boolean saying whether or not the generator has anything left to yield. Generators are made using the function* and yield keywords. I’ll show you a simple generator which generates 1, then 2, then 3, then stops.

function* threeNumbers(){
    yield 1;
    yield 2;
    yield 3;
}

To use this generator, assign the result of calling threeNumbers to a variable, then use it’s method next, like so:

let a = threeNumbers();    // note that we're not using `new` here
a.next();                  // {value: 1, done: false}
a.next();                  // {value: 2, done: false}
a.next();                  // {value: 3, done: false}
a.next();                  // {value: undefined, done: true}

This is a trivial generator, and can hardly be said to do anything useful. It is possible to create an infinite generator–any valid JS code can be used inside a generator. Each time the generator encounters yield it will pause what it is doing and return a value. Once next is called again, it will resume.

Generators can also take arguments. The arguments taken by generators become part of each GeneratorFunctions scope. To show this, let’s look at a generator that generates multiples of a given number.

function* multiplesOf(num){
    let counter = 0;
    while(true){
        yield counter;
        counter += num;
    }
}

let of3 = multiplesOf(3);
of3.next().value;           // 0
of3.next().value;           // 3
of3.next().value;           // 6
of3.next().value;           // 9
// ... etc.

Practical use–Symbol.iterator

While generators may be interesting, you are probably wondering how they are practical. Enter Symbol.iterator. An objects Symbol.iterator dictates how it behaves in for..of loops. Here’s a simple Collection class. (Note that *name(){...} in a class statement is a generator!)

class Collection {
    constructor(){
        this.vals = [];
    }

    get size(){
        return this.vals.length;
    }

    retrieve(ind){
        return this.vals[ind];
    }

    add(val){
        this.vals.push(val);
    }

    *[Symbol.iterator](){
        for(let i = 0; i < this.size; i++){
            yield this.retrieve(i);
        }
    }
}

let a = new Collection();
a.add(5);
a.add(3);
a.add("Hi!");
for(let p of a){
    console.log(p);
}
// logs:
// 5
// 3
// Hi!

The spread operator

An iterable object is an object for which it’s Symbol.iterator is well-defined, as above. Simple! Not only is the Symbol.iterator used for for..of loops, it’s also used with the spread operator. The spread operator applies the yield of the iterable to a function, array, etc. For example, a string is iterable. Observe:

for(let i of "Hi!"){
    console.log(i);
}
// logs:
// H
// i
// !

Thus, we can apply its members to an array, or to a function:

console.log([..."Hi!"]);    // ["H", "i", "!"]

let f = (x, y, z) => y + z + x;
console.log(f(..."Hi!"));   // "i!H"

What I love about the spread operator is that you can use it as a function argument operator! When used in this way, it’s called the “rest paramter”.

// returns it's arguments as an array
// That is, `a` contains all the arguments of the function
let f = (...a) => a;

// `b` always contains the first argument, and `a` contains the rest
// `a` is an empty array when only one argument is given.
let g = (b, ...a) => [b, a];

// however, functions like (...a, k) are not valid -- the rest parameter
// can only be used as the last argument to a function.

Array interaction with the spread operator

Say you have an array A and a function f. Here is a chart dictating some helpful results.

f = (a) => a;      f(A)     equals     A
f = (...a) => a;   f(A)     equals     [A]
f = (a) => a;      f(...A)  equals     A[0]
f = (...a) => a;   f(...A)  equals     A

My personal thoughts on the rest paramter

While I like the rest paramter, I feel as if it could’ve been defined better. For example, there is little ambiguity when I write (...a, b)–I refer to an array a containing all but the last argument, which then is b. The question then is what the behaviour would be when only a single argument is passed. I would say that a should contain a single member, and let b be undefined, though I’m sure there would be contention on this subject.

Suggested reading