Skip to content

Instantly share code, notes, and snippets.

@Anna-Myzukina
Last active September 23, 2018 19:00
Show Gist options
  • Save Anna-Myzukina/1029bfb5da5af5db0fba4bc69f1ac7c7 to your computer and use it in GitHub Desktop.
Save Anna-Myzukina/1029bfb5da5af5db0fba4bc69f1ac7c7 to your computer and use it in GitHub Desktop.
esnext-generation
//Exercise 1
/*
Create a function, makeCounter(someObj), which turns someObj (a plain old
Javascript Object) into an Iterator. This Iterator should count the positive
integers starting at 1, through to and including 10
An Iterator's .next() method must always return an object similar to
{value: 1, done: false}.
Once all numbers have been returned, done should be true.
*/
module.exports = function makeCounter(someObj) {
var num = 0,
done = false;
someObj.next = function() {
if (num < 10) {
num++;
} else {
done = true;
}
return {
value: num,
done: done
}
}
}
/*A familiar concept in many other languages, Iterators have landed as a new
Javascript feature in the ES6 standard.
"Iterators are nothing more than objects with a certain interface."[1]
That interface consists of a single method .next() which returns an object
with two keys;
* `value`: the next item in the Iterator's collection.
* `done`: true when the Iterator's collection is exhausted.
Every time next() is called, the Iterator's collection is advanced one more
item, and that item is returned as the value.
Once all the items in the collection are exhausted, value will be the
Iterator's "return value", and done will be true.
All subsequent calls to next() will have done set to true.*/
//Exercise 2
/*Write a function filterForNumbers(iterable) which extracts only the numeric
values from iterable, returning those values as an Array. iterable could be
any built-in Iterable, or a custom Iterable exposing an Iterator via the key
Symbol.iterator
- Iterating Arrays Using `.forEach()`
- Iterating Objects Using `for...in`
- Iterating Iterables Using `for...of`
*/
module.exports = function filterForNumbers(iterable) {
// loop over iterable, adding numeric values to a new array
var numbers = [];
for (var item of iterable) {
if (typeof item === 'number') {
numbers.push(item);
}
}
// then return the new array of numbers
return numbers;
}
/*Every array has methods on its prototype, allowing iteration of its numeric keys
(aka: array indexes). The most simplest in form is the .forEach() method which
very closely mimics the behaviour of the traditional for construct.
## Iterating Objects Using for...in
The for...in construct allows a shorthand approach to iteration of Object keys
without having to worry about holes (non-contigious numeric keys), non-numeric
keys, or if the key is on the object directly, or on the prototype.
## Iterating Iterables Using for...of
Instead of using two different methods to iterate depending on the type (Object
or Array), ES6 provides a new construct for...of[1], allowing iteration over
any collection that implements the Iterable interface!
In ES6, Arrays, TypedArrays, and the new Map & Set collections all implement the
Iterable interface. For example:
for(var i of [1, 2, 3]) {
console.log(i);
}
// Output: 1 2 3
Iterables expose their Iterator via the key Symbol.iterator (also written as
@@iterator)[2], allowing the for...of construct access to call .next() for
us:
var arr = [1, 2, 3];
var itr = arr[Symbol.iterator]();
typeof itr.next; // 'function'
# Notes
* [1]: not to be confused with iterating over objects via `for...in`
* [2]: ES6 also introduces other common Symbols: http://bit.ly/js-symbols*/
//Exercise 3
/*Write a function to generate iterators, generate(), that returns an infinite
Iterator. i.e.; an Iterator that never returns done: true[1].
The Iterator should return all even numbers >= 2 if the first parameter to
generate() is true, and return all odd numbers >= 1 if the first parameter
to generate() is false.
Also add the ability to swap between even <-> odd iterators mid-iteration by
accepting a bool swap to .next(). For example, if the last call to .next()
returned 2, calling .next(true) immediately afterward should return 3.
Copy this boilerplate to a new file and complete with your solution, then
execute esnext-generation verify <your-file.js> to verify it.
module.exports = function generate(isEven) {
// return an Iterator for even numbers if isEven is true
// or, return an Iterator for odd numbers if isEven is false
// If `.next(swap)` receives `true`, swap between even <-> odd
}
## Notes
* [1]: If the key `done` is left off, it is assumed to be `false`.
*/
module.exports = function generate(isEven) {
// return an Iterator for even numbers if isEven is true
// or, return an Iterator for odd numbers if isEven is false
// If `.next(swap)` receives `true`, swap between even <-> odd
var num;
if (isEven) {
num = 0;
} else {
num = -1;
}
var itr = {
next: function (swap) {
num += (swap ? 1 : 2);
return {
value: num,
};
},
};
return itr;
};
/**A lot of thought went into designing the Iteratabls specification: The .next()
method was chosen to allow both general usage iteration (as we have seen in the
previous lessons), but also to enable more advanced techniques.
The creation of Iterators is an area with a lot of flexibility. In Introduction
To Iterators, we created an Iterator that iterated a fixed set of numbers.
What if we wanted to create an Iterator that iterated a dynamic set of numbers,
or any other data?
Both the function which creates the Iterator, and the .next() method on the
Iterator itself can accept any arbitrary parameters - they're only functions
after all.
This is where the power of Iterators lies: usage of .next() combined with the
ability to generate dynamic sets of numbers or data gives us a unified interface
for defining not only finite number sequences, but also abstract sequences, and
operations on those abstract sequences. */
//Exercise 4
/*Rewrite the function we created in Advanced Iterables to be a generator: It
must accept the same parameter isEven, and return the set of all even or odd
numbers. Don't worry about the swap argument this time, we will cover that next.
*/
module.exports = function* generate(isEven) {
// `yield` either odd or even numbers based on `isEven`
var num = isEven ? 0 : -1;
while (true) {
num += 2;
yield num;
}
};
/**So far we've seen as Iterators advance from being simple built-in constructs (on
Array, Set, Map, etc) to more complex dynamic constructs (infinite number
sequences, etc), the code can begin to get more verbose, with lots of
boilerplate required.
Wouldn't it be nice if there was an easier way to use dynamic iterators?
Something that could hide all the minutiae of creating an iterator and calling
.next() on it?
Say hello to Generators.
Generators take what we learned in Advanced Iterables, and wraps them in some
new ES6 syntactic sugar. Instead of returning an object containing a .next()
method which returns the value, we use the new yield keyword to return the
value directly from within the generator.
To specify a generator, you add an asterisk * after the function keyword.
For example, a generator which returns all integers from 1 to Infinity can be
written as:
1. function *generate() {
2. var num = 0;
3.
4. while(true) {
5. num += 1;
6. yield num;
7. }
8. }
Notice we have done away with all the .next() boilerplate, and replaced it
with the yield keyword to return the value. Behind the scenes, an Iterator is
generated for us (hence the name Generators), which has all the regular
properties of an Iterator (.next(), etc).
## yield
yield is essentially a replacement for having to explicitly return an object
with a .next(). When executed, it will return the given value, and halt
execution of the generator function. As soon as .next() is called on the
generator's Iterator, execution will continue from exactly where yield left
off.
To execute the above example is the same as for any Iterator; you can use it
inside a for(... of ...) construct:
for(var i of generate()) {
console.log(i); // 1 2 3 4 5 ... Infinity
}
(remember for(... of ...) calls .next() for us internally)
So, in our example, the code would be evaluated line by line as:
num = 0; // line 2
while(true); // line 4
num = 1; // line 5
yield 1; // line 6. Execution halts here. 1 is returned
// When `.next()` is called on the iterator, execution would continue from line 6
while(true); // line 4
num = 2; // line 5
yield 2; // line 6. Execution halts here. 2 is returned
// ... and so on every time `.next()` is called
By replacing the .next() and the cumbersome boilerplate with two new peices of
syntactic sugar we have a readable, easy to use function which creates
Iterators.
*/
//Exercise 5
/*Write a generator, *multiplier() which yields all the integers from 1 to
Infinity multiplied by a value passed in to the .next() call.
That is, your generator should satisfy the following tests:
var it = multiplier();
console.log(it.next().value) // 1
console.log(it.next().value) // 2
console.log(it.next(2).value) // 6 (2 * 3)
console.log(it.next(5).value) // 20 (5 * 4)
console.log(it.next(3).value) // 15 (3 * 5)
// ... etc
*/
module.exports = function *multiplier() {
// `yield` all integers multiplied by the value passed in via `.next()`
var num = 0,
multiplier = 1;
while(true){
num++;
multiplier = yield num * multiplier;
if(!multiplier){
multiplier = 1;
}
}
}
/*As we saw in Advanced Iterables, another interesting and powerful ability of
Iterators is passing arguments into .next().
For a regular Iterable which defines its own Iterator and .next() method, we
can accept the argument as a regular function parameter:
module.exports = function allIntegers() {
var num = 0;
var myIterator = {
next: function(reset) {
if (reset) {
num = 0;
}
return {
value: num++;
}
}
}
return myIterator;
}
Here, we pass the reset argument into .next() like so:
var it = allIntegers();
console.log(it.next()); // {value: 1, done: false}
console.log(it.next()); // {value: 2, done: false}
console.log(it.next()); // {value: 3, done: false}
console.log(it.next(true)); // {value: 1, done: false}
console.log(it.next()); // {value: 2, done: false}
Since generators return an Iterator, arguments can still be passed into the
.next() as above, but to utilise these arguments within the generator is a
little bit different. We must listen to the return value of a call to yield:
function *generate() {
var num = 0,
reset = false;
while(true) {
num += 1;
reset = yield num;
if (reset) {
num = 0;
}
}
}
(Note: This is a slightly modified example from Introduction To Generators)
There is one important difference between the return value of yield and a
regular Iterator: a generator's Iterator must have .next() called at least
once without any parameters.
Put another way: The expression reset = yield num; is evaluated right-to-left.
The first call to .next() will execute up to and including yield num;, at
which point execution of the generator will halt, then the following call to
.next(<value>) will evaluate reset = ..., capturing the <value> passed in.
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment