Last active
September 23, 2018 19:00
-
-
Save Anna-Myzukina/1029bfb5da5af5db0fba4bc69f1ac7c7 to your computer and use it in GitHub Desktop.
esnext-generation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//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.*/ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//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*/ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//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. */ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//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. | |
*/ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//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