Functional Programming With Javascript: Generator, Lazy functions
We made basic util functions map, filter, reduce, and so on. In this posting, we’ll make additional functions using ‘Generator’.
Generator
The Generator object is returned by a generator function. It’s an iterator so we can access its value sequentially.
function* generator() {
console.log('one'); yield 1;
console.log('two'); yield 2;
console.log('three'); yield 3; console.log('four');}const iterable = generator();console.log(iterable);
console.log(iterable.next());
console.log(iterable.next());
console.log(iterable.next());
console.log(iterable.next());----------------------------- Console -----------------------------Object [Generator] {}
one{ value: 1, done: false }
two
{ value: 2, done: false }three
{ value: 3, done: false }four
{ value: undefined, done: true }
Looking at the code above, iterable waits for calling the ‘next’ function and returns {value, done}. The generator function stops at the ‘yield’ statement. And when the ‘next’ function is called, continue the next line.
Why do we need this feature? Lazy evaluation can reduce unnecessary calculation. Sometimes, we don’t have to calculate all elements of the collection(For example, we need just the first-page list). We can get the correct result by calculating only the number of elements that are finally needed.
Expressed in pictures, a normal map evaluates all of the function’s values and then starts the next function.
But lazy function evaluates to the final function one by one.
Lazy functions
Lazy map
So we can make a lazy map generator function.
function *map(collection, mapper) { for (const value of collection) { yield mapper(value); }}module.exports = map;
‘for … of …’ statement and spread syntax can access iterable’s values. The lazy map yields a mapped value.
test('map', () => { const squared = map([1, 2, 3, 4, 5], (value) => { console.log(value); return value * value; }); expect(squared.next().value).toBe(1);
expect(squared.next().value).toBe(4);});----------------------------- Console -----------------------------
1
2
The test case success. The map function squares the elements. The difference is ‘iteratee’ function is called just twice! It’s more efficient:)
Lazy filter
function* filter(collection, predicate) { for (const value of collection) { if (predicate(value)) { yield value; } }}module.exports = filter;
Similar to the ‘map’, we can implement ‘filter’.
test('filter', () => { let called = 0; const odds = filter([1, 2, 3, 4, 5], (value) => { called++; return value % 2; }); expect(odds.next().value).toBe(1); expect(odds.next().value).toBe(3); expect(called).toBe(3);});
In this code, to get the secondly filtered element, the iteratee function has to be called three times(because 2 is not an odd number).
The ‘reduce’ function must calculate to the end of the element so lazy is not exist.