[Javascript] Getter and Setter Abstractions

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:

原文地址:https://www.cnblogs.com/Answer1215/p/9043261.html