It's about Iterables and Iterators
Garrett Dawson
@killtheliterate
I've been a front-end dev of 5 years. I work at VictorOps
I love JavaScript
What will people discover with es2015 onwards?
Ada Lovelace and the Analytical Engine
* Loops began with Ada Lovelace. Bernoulli numbers and the analytical engine. * This wasn't software, but it was programming. * https://programmers.stackexchange.com/questions/149465/who-created-the-ideas-of-the-first-loop-constructs
****** Algol 60 ******
FOR i:=1 UNTIL 5 DO
FOR j:=1 UNTIL i DO
OUTTEXT("*");
OUTLINE
* COBOL
IDENTIFICATION DIVISION.
PROGRAM-ID. Display-Triangle.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 Outer-Counter PIC 9.
01 Inner-Counter PIC 9.
PROCEDURE DIVISION.
PERFORM VARYING Outer-Counter FROM 1 BY 1 UNTIL 5 < Outer-Counter
PERFORM VARYING Inner-Counter FROM 1 BY 1
UNTIL Outer-Counter < Inner-Counter
DISPLAY "*" NO ADVANCING
END-PERFORM
DISPLAY "" *> Output a newline
END-PERFORM
GOBACK
.
'Visual Basic
Public OutConsole As Scripting.TextStream
For i = 0 To 4
For j = 0 To i
OutConsole.Write "*"
Next j
OutConsole.WriteLine
Next i
for
// loopable
const theThing = ['hello', 'humans', 'in', 'this', 'room']
// the loop
for(let i = 0; i < theThing.length; i = i + 1) {
console.log(theThing[i]) // hello, humans...
}
counter
Ancient and venerated, Similar to many other langs
Have to track STATE of the loop, terminated at CONDITION
Obviously, problems! STATE
We have to be very verbose about what's happening in our loopfor-in
// loopable
const otherThing = ['hello', 'humans', 'in', 'this', 'room']
// the loop
for(let i in otherThing) {
console.log(otherThing[i]) // hello, humans...
}
we use the counter to access the loopable
Higher level, no state
Less how, more what...iterate on all enumerable properties
// loopable
const theCollection = ['hello', 'humans', 'in', 'this', 'room']
// shwhoops
theCollection.alsoEnumerable = 'enumerated'
// the loop
for(let i in otherThing) {
console.log(otherThing[i]) // hello, humans... enumerated
}
the collection types we're used to don't distinguish
for-in enumerates all props on object
which is to say, it enumerates all enumerable properties
scope continues to be a problemArray#applicatives
// loopable const say = ['hello', 'humans', 'in', 'this', 'room'] // weird... say.justLeavingThisHere = 'boo' // the loop otherThing.forEach(el => console.log(el)) // hello, humans...And of course, we have map, filter and reduce Only arrays, strictness
// loopable const theIterable = [1,2,3] // the loop theIterable.forEach(el => console.log(el)) // 1, 2, 3Given these commonalities, TC39 identifies Iterable/Iterator
Builtin Iterators
// an iterable
const theIterable = 'a string is a list of characters'
// an iterator
// access the member directly
// instead of by index
for(let char of theIterable) {
console.log(char)
}
Looks similar to for-in
access member directly
// an iterable
const theIterable = ['an', 'array', 'is', 'iterable']
// an iterator
const newArray = [...theIterable]
// arguments is iterable
const fn = (...args) => { args.forEach(el => console.log(el)) }
fn(1, 2, 3)
Looks alot like [].map()
The arguments object can be passed to an iterator// an iterable const theCollection = ['hello', 'humans', 'in', 'this', 'room'] // an iterator let [these, are, pieces, ...iterable] = theCollection console.log(these) // 'hello' console.log(iterable) // ['this', 'room']Also looks alot like [].map() Composable with ...spread to collect the rest Notably, objects can be destructured, but that's outside this scope
The future is in the future
// array comprehension
const newArray = [for (i of [ 1, 2, 3 ]) i * i ]
console.log(newArray) // 1, 4, 9
// generator comprehension
const newGen = (for (i of [ 1, 2, 3 ]) i * i )
console.log(newGen.next()) // {value: 1, done: false}
console.log(newGen.next()) // {value: 4, done: false}
I can't wait, Also like [].map()
Another way to extract pieces of a list
Check it out in the Babel REPLBuiltin Iterables
// an iterable
const theIterable = 'this is iterable'
// an iterator
for(let char of theIterable) {
console.log(char) // each char
}
// an iterator
const chars = [...theIterable]
console.log(chars) // ['t','h','i','s',' '...]
Since they are iterable, they are consumable by iterators
// an iterable
const theIterable = ['this', 'is', 'iterable']
// an iterator
for(let words of theIterable) {
console.log(words) // 'this' 'is'...
}
// an iterator
const sentence = [...theIterable]
console.log(sentence) // ['this', 'is', 'iterable']
// an iterable
const theIterable = new Set()
theIterable.add(1)
theIterable.add(3)
theIterable.add(2)
theIterable.add(2)
// an iterator
for(let num of theIterable) {
console.log(num) // 1, 3, 2
}
// an iterator
const numbers = [...theIterable]
console.log(numbers) // [1,3,2]
// an iterable
const theIterable = new Map()
theIterable.set('one', 1)
theIterable.set('three', 3)
theIterable.set('two', 2)
// an iterator
for(let numTuple of theIterable) {
console.log(numTuple) // ['one', 1]...
}
// an iterator
const tuples = [...theIterable]
console.log(tuples) // [['one', 1]...]
// an iterable
const maker = function* () {
yield 1
yield 2
yield 3
}
const theIterable = maker() // this gives us back a generator object
const first = theIterable.next() // which we can step through
console.log(first) // {value: 1, done: false}
yield keyword exits the function
execution resumes where the yield left off
// an iterable
const maker = function* () {
let index = 0 // the mutated state will be persisted
while(index < 3) {
yield index++
}
}
const theIterable = maker() // this gives us back a generator object
// which we can operate on with an iterator
// this works because the generator is finite
const nums = [...theIterable] // [0,1,2]
It's notable that using spread transforms the values into normal stuff
const maker = function* () {
let first = yield
let second = yield first
let third = yield second
yield
}
const echo = maker()
console.log(echo.next(1))// {value: null, done: false}
console.log(echo.next(500)) // {value: 500, done: false}
console.log(echo.next('sup')) // {value: 'sup', done: false}
We can push values into Generators
If we assign the last yield to a variable, that variable becomes
whatever we push with next()
const anIterable = 'I am iterable'
const also = ['so', 'am', 'i']
for(i of anIterable) {
console.log(i)
}
for(i of also) {
console.log(i)
}
If it is an Iterable, you can consume it with an iterator
We can codify these patterns in protocols{ }.next()
An object is an iterator when it knows how to access items from a collection one at a time, while keeping track of its current position within that sequence "how do i loop?" this is why a generator is both iterable and iterator...for a linked list
http://bit.ly/1OVllSX
// this is what a linked list looks like
{val: 1, rest: {val: 2, rest: {val: 3, rest: {val: 4, rest: null}}}}
// basically an array
[1, 2, 3, 4]
quick aside about linked lists
why are they useful? memory and known size
collection types in FP are usually linked lists
you walk them recursively[Symbol.iterator]
The iterable protocol allows JavaScript objects to define or customize their iteration behavior, such as what values are looped over in a for..of construct. when you define a constructor, give it this property...still a linked list
http://bit.ly/1MVVXxN
// give our type an iteration protocol const iterable = LinkedList()[Symbol.iterator] = () => // etcwe can make a linked list type that describes how it should be walked this makes our type "iterable"
possible infinity
http://bit.ly/1DzRyhL
What does it do?
It's about Iterables and Iterators
Garrett Dawson
@killtheliterate