Functional Programming With Javascript: Generator, Lazy functions

Woohyun Jang
3 min readDec 3, 2020

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.

Normal functions

Expressed in pictures, a normal map evaluates all of the function’s values and then starts the next function.

Lazy functions

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.

Github Source Code

--

--