JavaScript provides primitive types and means of processing those. However, those are not enough. Real data must somehow come into the program and data must somehow leave the program, for it to become useful to us. In this talk, we will see how two abstractions are essential for data flow and to build up other abstractions, such as Iterator, Iterable, Observable, Scheduling, and others. Talk
Getter as abstractions:
const ten = 10; // Abstractions // Laziness const getTen = () => { console.log("hi"); // hook for side effects // Implementation flexibility return 5 + 5; // return 2 * 5; // return 10 }
Benefits:
- First of all: 'getTen' if you don't call the function, the calcuation inside the fucntion never run.
- You can do any side effects inside function.
- Implmentation flexibility, you can do different ways to aecieve the same effects.
Think about this code:
function add(getX, getY) { const x = getX(); const y = getY(); return x + y; }
'getX' and 'getY' are abstract. What we are doing now is adding two abstract number in concrete world. What if we add in abstract world?
function add(getX, getY) { return () => { // put code into a get getter function const x = getX(); const y = getY(); return x + y; } }
Now the function are complete lazy, lazyniess is a good thing, when you have some lzay you use getter can make things concrete.
function add(getX, getY) { return () => { const x = getX(); const y = getY(); return x + y; } } const getTen = () => 10; const getY = Math.random; const getSum = add(getTen, getY); // so far no calcuation happens
Lazy initialization and lazy iteration:
let i = 0; const array = [10,20,30,40]; function getArrayItem() { // lazy iteration return array[i++]; } console.log(getArrayItem()); console.log(getArrayItem()); console.log(getArrayItem()); console.log(getArrayItem());
console.log(getArrayItem()); // undefined
This is a concrete example, we call the fucntion mutiplue time and loop though the array.
And we also see that after call result as 'undefined', if we want to loop the array again from zero idnex, we have to do:
let i = 0; const array = [10,20,30,40]; function getArrayItem() { // lazy iteration return array[i++]; } console.log(getArrayItem()); console.log(getArrayItem()); console.log(getArrayItem()); console.log(getArrayItem()); i = 0; console.log(getArrayItem()); console.log(getArrayItem()); console.log(getArrayItem()); console.log(getArrayItem());
We call
i = 0;
Of course this is not good at all, we leak the information.
The way to improve it is by using getter & lazyniess:
function getGetArrayItem() { // lazy initialization let i = 0; const array = [10,20,30,40]; return function() { // lazy iteration return array[i++]; } } let getArrayItem = getGetArrayItem(); console.log(getArrayItem()); console.log(getArrayItem()); console.log(getArrayItem()); console.log(getArrayItem()); getArrayItem = getGetArrayItem(); console.log(getArrayItem()); console.log(getArrayItem()); console.log(getArrayItem()); console.log(getArrayItem());
getter-getter: Abstract List:
Think about the following example:
function range(left, right) { return () => { // lazy initialization let x = left; return () => { // lazy iteration if (x > right) { return undefined; } return x++; } } } const getGet = range(10 , 14); const get = getGet(); console.log(get()); // 10 console.log(get()); console.log(get()); console.log(get()); console.log(get()); // 14 console.log(get()); // undefined console.log(get()); console.log(get());
It prints out the range of nuber, which we defined, we can notice after the right limit of the number, it just print out undefined.
One way to solve the undefined problem is using for loop:
function range(left, right) { return () => { // lazy initialization let x = left; return () => { // lazy iteration if (x > right) { return undefined; } return x++; } } } const getGet = range(10 , 14); for(let get = getGet(), x = get(); x !== undefined; x = get()) { console.log(x) // 10 ... 14 }
The good thing about this code is that no matter how large the range it is, the menory size is always 1. Every time you call the fucntion, it pull one value from the abstract getter function every time. It makes CPU and memory efficient.
Completion markers:
function range(left, right) { return () => { // lazy initialization let x = left; return () => { // lazy iteration if (x > right) { return {done: true}; } return {done: false, value: x++}; } } } const getGet = range(10, 14); for(let get = getGet(), res = get(); !res.done; res = get()) { console.log(res.value) // 10 ... 14 }
We added {done: true | false} as a complete marker.
Convert to Symbol.iterator:
function range(left, right) { return { [Symbol.iterator]: () => { // lazy initialization let x = left; return { next: () => { // lazy iteration if (x > right) { return {done: true}; } return {done: false, value: x++}; } } } } }
Javascript notice that when you are using [Symbol.iterator] and have 'next' inside, then it provides you a nice syntax to loop over the iterator and get the value of out it.
for(let x of range(10, 14)) { console.log(x) // 10 ... 14 }
We might have done this:
function range(left, right) { return { [Symbol.iterator]: () => { // lazy initialization let x = left; return { next: () => { // lazy iteration if (x > right) { return {done: true}; } return {done: false, value: x++}; } } } } } for(let x of range(0,14)) { if(x % 2 === 0) { console.log(x) } }
We using 'if' inside 'for' loop, well it is nothing wrong, but we can do better. Because range() is abstract function, we don't need to pull all the value done to the concrete world to do the filtering, we can also do the filtering in abstract function.
const filter = pred => iterations => { let z = []; for (let x of iterations) { if(pred(x)) z.push(x); } return z; }; function range(left, right) { return { [Symbol.iterator]: () => { // lazy initialization let x = left; return { next: () => { // lazy iteration if (x > right) { return {done: true}; } return {done: false, value: x++}; } } } } } for(let x of filter(x => x % 2 === 0)(range(0,14))) { console.log(x) }
Setter-setter abstraction:
You can think of "setter-setter" is callback:
const setSetTen = (setTen) => { setTen(10) } setSetTen(console.log) // 10
The benifits of doing setter-setter is
- Async
- Inversion of control
const setSetTen = (setTen) => { setTimeout(() => { //Async setTen(10) }, 1000) } setSetTen(console.log) // 10
Setter-setter to Observable: