43

thank u, [Symbol.iterator].next - Modular iteration without cloning or modifying...

 5 years ago
source link: https://www.tuicool.com/articles/hit/VNVNBjI
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

thank u, [Symbol.iterator].next

VjmQr2Z.jpg!webvAjIJzv.jpg!web
thank u, next album art

Thank u, [Symbol.iterator].next

Thank u, [Symbol.iterator].next

I’m so f****in’ grateful for this spec

Symbol.iterator

Symbol.iterator is the protocol that makes native objects like Array , Set , and Map iterable by providing a hook into language features like for…of loops and the spread operator .

The most obvious use case is in creating new, iterable data structures that are not provided by the language, like a Linked List . However, Symbol.iterator also lets developers redefine navigation patterns without cloning the object or modifying its order in-place .

These patterns can then be written once in a function, class, or module and used anywhere. That means you can loop through an Array forwards, backwards, randomly, or infinitely in a modular fashion.

emiy6jE.png!web

The actual Symbol.iterator protocol is very simple. It includes:

  • Iterable : an object with a function whose key is Symbol.iterator
  • Iterator : the above function, used to obtain the values to be iterated

In terms of code:

A3EbAje.jpg!web

This tells the Javascript runtime that an Object is iterable. It provides a way to get the next value and check if iteration is done .

If you want more details, there is a great guide on Demystifying ES6 Iterables & Iterators . Otherwise, let’s look at how to use this in practice.

Going Backwards

The two most common ways to iterate an array in reverse are using indices (from array.length — 1 to 0 ), or with Array.reverse :

A3EbAje.jpg!web

But both approaches raise concerns. With indices, it can be easy to forget the -1 , leading to the infamous off-by-one error . Using Array.reverse modifies the array in place, which may not always be the intention.

There are entire articles dedicated to this topic, but since we are talking about Symbol.iterator , let me suggest one more way:

A3EbAje.jpg!web

Rather than iterating backwards on a given array, we have defined the “going backwards” iteration behavior for any array, all without modifying the order or creating a clone of the original array.

Here is how reverse would work in practice:

A3EbAje.jpg!web

Another important aspect of Symbol.iterator is that we do not need an Array-like object to iterate. We can write a function like Python’s range and use it to iterate elegantly over a sequence of numbers.

A3EbAje.jpg!web

Note: this implementation of range is very simple. It has not been tested and has clear issues with infinite ranges and negative steps.

We can use range the same way we did reverse :

A3EbAje.jpg!web

We can even copy Scala’s Int#to syntax, though you should be cautious when extending a native object’s prototype .

A3EbAje.jpg!web

Note: I wrapped the number literal in parentheses to avoid a SyntaxError, but there are other options .

As we will see, with Symbol.iterator the possibilities are literally endless!

Thank U, Next

3iQnAja.png!webvaAremn.png!web

A music player is a great example of iteration behavior. Most of us do not listen to songs in order. Instead we control playback with shuffle and repeat .

As a programmer, this means building a way to play songs infinitely (repeat), randomly (shuffle), or both. Here is an example that does just that:

67faquE.png!web
Thank U, Next: Album Navigation using Symbol.iterator

The logic underlying these playback options is implemented using Symbol.iterator . Let me quickly break down how each class works.

Repeat

Playing songs on repeat is the simplest task. With InfiniteArray , when we reach the end of the array we just go back to the first element, ad infinitum .

A3EbAje.jpg!web

Caution: InfiniteArray might be the simplest, but it is also the most dangerous! You can easily freeze a tab by calling [...infiniteArray] .

Shuffle

Popular music services like Spotify used to shuffle songs with the Fisher-Yates algorithm , “but users complained that the playlist wasn’t genuinely random.”

Thankfully this is just an example, and Fisher-Yates is by far the simplest shuffling algorithm so we will go with that.

A3EbAje.jpg!web

While RandomArray does not modify the Array in place, we can use it to create a shuffled (randomly-sorted) copy of the original Array.

A3EbAje.jpg!web

Note: The actual order will vary each time the spread operator is called.

Repeat & Shuffle

Some users will enable both shuffle and repeat simultaneously. RandomInfiniteArray does just that, iterating both randomly and infinitely.

A3EbAje.jpg!web

Caution: same warning as InfiniteArray , be careful looping infinitely!

Each of these classes can be used anywhere an Array is expected. In this example, updateIterator creates a shallow copy of the original album list with a type that depends on which playback options are selected.

A3EbAje.jpg!web

In a larger application, Symbol.iterator can cleanly encapsulate iteration behavior in classes or functions , decreasing code repetition and error risk.

Break Free

JrIbIje.jpg!webaimmuaR.jpg!web

It is time to break free from the standard for loop! Thus far, I have shown how Symbol.iterator offers significant advantages by encapsulating iteration patterns for a single array. What about multiple arrays?

Looping over two arrays pairwise in Vanilla JS quickly litters code with [i] , but because this is a common task libraries like Lodash come with a _.zip function that combines multiple sequential collections into one collection.

A3EbAje.jpg!web

Using variadic arguments and Symbol.iterator , we can easily write our own version of zip that iterates over multiple arrays but without needing to clone the entirety of each array .

A3EbAje.jpg!web

Now we have a modular and readable way to iterate over multiple arrays at the same time! Extending the previous example to include a third dimension:

A3EbAje.jpg!web

In terms of performance, it seems the Symbol.iterator version of zip is significantly faster than the one that clones arrays, while the range example above is comparable to a standard for loop.

Of course accurate, generalizable benchmarks are difficult and it is important to measure in performance-critical settings. But generally speaking, Symbol.iterator uses less memory and should be at least as fast, if not faster , than other approaches.

Yet another use of iterators, is to stop iterating!

A3EbAje.jpg!web

This is similar to _.takeWhile , although again it does not require cloning.

A3EbAje.jpg!web

One Last Time

QjE3mya.jpgMjIFjme.gif
iOS “Invisible Ink” Messages

Let me end with one more example. In the world of Snapchat and ephemeral messaging, what if we could create an Array using invisible ink ?

Ok, not literally but you get the idea.

A3EbAje.jpg!web

That is what I call VanishingArray , an Array that removes elements as you loop through them. Iterate once, and only once.

Before you move on too fast, it should be noted that Symbol.iterator has its downsides and should probably not the default approach to every problem.

For one, it is often more complicated and verbose than simply chaining a combination of Array.filter and Array.map . There are also other approaches with equal or better performance characteristics, like data streams and libraries like Sequency .

That said, Symbol.iterator is versatile and efficient , and I have only presented a small subset of its utility. MDN has examples using generators with Symbol.asyncIterator (that’s right, asynchronous iterators!). If you are curious about the Symbol API more generally, Keith Cirkel has a great article on Metaprogramming in ES6 .

If you found this helpful, say thank u before moving on to the next article :clap:

And follow me on LinkedIn · GitHub ·Medium


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK