Presented at Front Conference Zurich 31 August - 1 September 2017
Video at https://vimeo.com/232221648
var ponies = [
"AppleJack",
"Fluttershy",
"Pinkie Pie",
"Rainbow Dash",
"Rarity",
"Twilight Sparkle"
];
With ES6 (aka ES2015), objects have methods .entries()
, .keys()
, .values()
, which return iterators.
var i = ponies.values();
console.log(i.next());
// { value: "AppleJack", done: false }
console.log(i.next());
// { value: "Fluttershy", done: false }
console.log(i.next());
// { value: "Pinkie Pie", done: false }
console.log(i.next());
// { value: "Rainbow Dash", done: false }
console.log(i.next());
// { value: "Rarity", done: false }
console.log(i.next());
// { value: "Twilight Sparkle", done: false }
// When the iteration is exhausted, .next() returns { done: true }
console.log(i.next());
// { value: undefined, done: true }
for (let i of ponies) { // or `ponies.values()`
console.log(i);
}
/*
AppleJack
Fluttershy
Pinkie Pie
Rainbow Dash
Rarity
Twilight Sparkle
*/
Using .entries()
produces an array at each step, containing the index and the value.
for (let i of ponies.entries()) {
console.log(i);
}
/*
[ 0, "AppleJack" ]
[ 1, "Fluttershy" ]
[ 2, "Pinkie Pie" ]
[ 3, "Rainbow Dash" ]
[ 4, "Rarity" ]
[ 5, "Twilight Sparkle" ]
*/
var i = ponies[Symbol.iterator]();
for (let i of ponies) { // or `ponies.values()`
console.log(i);
}
/*
AppleJack
Fluttershy
Pinkie Pie
Rainbow Dash
Rarity
Twilight Sparkle
*/
var ponies = {
// The iterator key is the arrow function that, when called, returns the it object with its own next() method.
[Symbol.iterator]: () => {
let c = 0;
let it = {
next: () => {
// a little state machine...
c += 1;
switch (c) {
// case 0: return { value: true };
case 1: return { value: "AppleJack", done: false };
case 2: return { value: "Fluttershy", done: false };
default: return { done: true };
}
}
};
return it;
}
};
var i = ponies[Symbol.iterator]();
for (let i of ponies) {
console.log(i);
}
/*
AppleJack
Fluttershy
*/
Define an infinite stream of numbers...
var infinity = {
[Symbol.iterator]: () => {
let c = 0;
let it = {
next: () => {
return { value: c++, done: false }
}
}
return it;
}
};
Try it manually...
var i = infinity[Symbol.iterator]();
console.log(i.next());
// { value: 0, done: false }
console.log(i.next());
// { value: 1, done: false }
console.log(i.next());
// { value: 2, done: false }
console.log(i.next());
// { value: 3, done: false }
console.log(i.next());
// { value: 4, done: false }
Do not try this at home, thank you.
for (let i of infinity) {
console.log(i);
}
We can iterate over the stream with another iterator that "takes" the first X values
var take = function(num, iterable) {
let o = {
[Symbol.iterator]: () => {
let c = 0;
let it = iterable[Symbol.iterator]()
return {
next: () => {
return c++ < num ? it.next() : { done: true };
}
};
}
};
return o;
};
for (let i of take(5, infinity)) {
console.log(i);
}
/*
0
1
2
3
4
*/
var map = function(fn, iterable) {
let o = {
[Symbol.iterator]: () => {
let it = iterable[Symbol.iterator]()
return {
next: () => {
let next = it.next();
return next.done ? next : { done: false, value: fn(next.value) };
}
};
}
};
return o;
};
var fn = (i) => { return i + 5 };
var it = take(5, infinity);
for (let i of map(fn, it)) {
console.log(i);
}
/*
5
6
7
8
9
*/
for (let i of take(5, map(i => i + 5, infinity))) {
console.info(i);
}
/*
5
6
7
8
9
*/
This way of writing iterators is clunky, full of boilerplate.
A generator is a special function with an asterisk, that generates an iterator - instead of using [Symbol.iterator]
notation.
var infinity = function* () {
let c = 0;
while (true) {
yield c++;
}
}
No [Symbol.iterator]
call required - but we have to call infinity()
instead.
var i = infinity();
console.log(i.next());
// { value: 0, done: false }
console.log(i.next());
// { value: 1, done: false }
console.log(i.next());
// { value: 2, done: false }
console.log(i.next());
// { value: 3, done: false }
console.log(i.next());
// { value: 4, done: false }
var take = function* (num, iter) {
let c = 0;
let next;
while(c++ < num && !(next = iter.next()).done) {
yield next.value;
}
};
Note that we have to call infinity()
, not just pass it in...
for (let i of take(5, infinity())) {
console.log(i);
}
/*
0
1
2
3
4
*/
var map = function* (fn, iter) {
let next;
while(!(next = iter.next()).done) {
yield fn(next.value);
}
};
for (let i of map(i => i + 'lol', take(5, infinity()))) {
console.log(i);
}
/*
0lol
1lol
2lol
3lol
4lol
*/
var fiveup = function* () {
let c = 0;
while (true) {
// lefthand c maps to a parameter passed in by `next()`
c = yield c;
c = c + 5;
}
}
var i = fiveup();
NOTE: The first next()
cal primes the iterator (so that c is 0, and the generator awaits the next iteration).
i.next();
Now pass in some arguments
console.log(i.next(4));
// { value: 9, done: false }
console.log(i.next(100));
// { value: 105, done: false }
var unit = v => new Promise(res => res(v));
console.log(unit(5));
// Promise { <state>: "fulfilled", <value>: 5 }
console.log(Promise.resolve(5));
// Promise { <state>: "fulfilled", <value>: 5 }
Promise generator...
var promises = function* () {
console.log( yield unit("omg") )
console.log( yield unit("wtf") )
console.log( yield unit("bbq") )
}
var i = promises();
// prime it first...
console.log(i.next());
console.warn(i.next('omg'));
// omg
// { value: Promise { "fulfilled" }, done: false }
console.log(i.next('wtf'));
// wtf
// { value: Promise { "fulfilled" }, done: false }
console.log(i.next('bbq'));
// bbq
// { value: undefined, done: true }
console.log(i.next());
// { value: undefined, done: true }
Resolve promises...
var run = (iter, val = null) => {
let next = iter.next(val);
if (!next.done) {
// Note the recursive call on run()
next.value.then(result => run(iter, result))
}
}
run(promises());
/*
omg
wtf
bbq
*/
var fallout = function*(url) {
let response = yield fetch(url);
let text = yield response.text();
console.log( text );
}
run(fallout('/generators/data/fallout.txt'));
No more then
boilerplate.
This section required some revision, especially the promise resolver and prop value check.
// DOES NOT WORK IN BROWSERS 4 SEPT 2019
// Promise.defer HAS BEEN DEPRECATED.
var run = (iter, val = null, done = Promise.defer()) => {
const next = iter.next(val);
if (!next.done) {
next.value.then(result => run(iter, result, done));
} else {
done.resolve(next.value);
}
return done.promise;
};
// SIMPLIFY THE DONE WITH THIS INSTEAD
var run = function(iter, val) {
let next = iter.next(val);
if (next.done) {
return Promise.resolve(val)
}
// Note the recursive call on run()
next.value.then(result => run(iter, result));
};
var ponies = {
pie: "Pony Pie",
dash: "Rainbow Dash"
};
run(function* () {
let dash = ponies.dash;
let pie = ponies.pie;
console.log(`${ dash } is friends with ${ pie }`)
}());
// Rainbow Dash is friends with Pony Pie
var maybe = val => {
return {
then: (fn) => {
return val != null ? fn(val) : null;
}
}
};
console.warn(maybe(ponies.pie).then(value => value));
// Pony Pie
// ReferenceError warning if key not in obj
var prop = (key, obj) => maybe(obj[key]);
// Fix that with...
var prop = (key, obj) => {
return key in obj ? maybe(obj[key]) : maybe(null);
}
console.log(prop("dash", ponies).then(value => value));
// Rainbow Dash
console.log(prop("twi", ponies).then(value => value));
// null
run(function* () {
let dash = yield prop("dash", ponies);
let pie = yield prop("pie", ponies);
console.log(`${ dash } is friends with ${ pie }`)
}());
// Rainbow Dash is friends with Pony Pie
run(function* () {
let dash = yield prop("dash", ponies);
let twi = yield prop("twi", ponies);
// SHOULD NOT PRINT BECAUSE maybe().then() does not call fn() on null!
console.log(`${ dash } is friends with ${ twi }`)
}());
// SHOULD *NOT* SEE THIS from the prop('twi', ponies) call:
// ReferenceError: reference to undefined property "twi"
Monads.
{
unit: the unit function ??? - var unit = (v) => new Promise(res => res(v));
bind: the `run` function ??? - I DON'T KNOW
}