본문 바로가기
JavaScript

[FP&ES6+] range & L.range

by _sweep 2021. 12. 22.

함수형 프로그래밍과 JavaScript ES6+ 강의를 듣고 정리한 내용입니다.

 

 

range

 

const range = length => {
    let i = -1;
    let res = [];
    while (++i < length) {
        res.push(i);
    }
    return res;
};

console.log(range(5));

const add = (a, b) => a + b;
var list = range(4);
console.log(reduce(add, list));

// output
// [0, 1, 2, 3, 4]
// 6

 

숫자를 인자로 받아 0부터 그 숫자까지의 값을 요소로 가지고 있는 배열을 리턴한다.

함수의 내부를 살피면 length라는 인자를 받고 i가 0부터 length의 값까지 while문을 돌며 배열 res에 삽입된다.

 

 

L.range

 

const L = {};
L.range = function* (l) {
  let i = -1;
  while (++i < l) {
    yield i;
  }
};

console.log(L.range(5));

const add = (a, b) => a + b;
var list = L.range(4);
console.log(reduce(add, list));

// output
// L.range {<suspended>}
// 6

 

제너레이터 함수를 이용하여 이터레이터를 만든다.

내부를 살펴보면 i가 0부터 l만큼 while문을 돌며 yield 키워드를 사용해 반환할 값인 i를 정의한다.

 

그렇기에 L.range(5)를 로그로 출력하라고 하면 위의 range 함수 처럼 배열이 출력되는 것이 아닌 이터레이터가 출력되지만 reduce를 통한 결과값은 동일하다.

 

 

range & L.range

 

range 함수의 결과값은 배열이고 L.range 함수의 결과값은 next()를 통해 값을 얻을 수 있는 이터레이터이다.

 

위의 코드에서 range 함수와 L.range 함수를 통해 만들어진 list에 대해 reduce(add, list)의 결과는 둘 다 6이다.

reduce(add, list)에 대해 range 함수가 어떻게 6이라는 결과값을 냈는지는 추론하기 쉽다.

range(4) => [0, 1, 2, 3] => 0 + 1 + 2 + 3 = 6 의 과정을 거친 것이다.

 

그러면 L.range는 어떻게 6이라는 결과값을 얻을 수 있었을까?

그 답은 reduce 함수가 이터러블을 인자로 받는 함수이기 때문이다.

 

const reduce = (f, acc, iter) => {
  if (!iter) {
    iter = acc[Symbol.iterator]();
    acc = iter.next().value;
  }
  for (const a of iter) {
    acc = f(acc, a);
  }
  return acc;
};

 

reduce 함수를 다시 복습하자면 다음과 같은 형태이다.

이때 인자로 iter를 받는 것을 볼 수 있다.

 

var list = range(4);
console.log(list);

// output
// [0, 1, 2, 3]

 

range 함수는 reduce에 인자를 전달하기 전 이미 list의 선언 단계에서 list에 배열이 저장된다.

다시 말하면 range 함수를 실행했을 때 그 값들이 즉시 배열로 평가된다는 것이다.

따라서 reduce(add, list)를 실행했을 때 list에는 배열이 들어갔다가 reduce 함수에서 이터레이터로 바뀌고 그 값을 활용해 add 함수를 실행해 나간다.

 

var list = L.range(4);
console.log(list);

// output
// L.range {<suspended>}

 

반대로 L.range는 L.range 함수로 선언해도 평가되지 않고 함수가 작동되지도 않는다.

이들이 평가되는 시점은 이터러블의 내부를 순회할 때이다.

즉, 평가가 필요할 때까지 기다렸다가 필요한 순간이 되면 값을 하나씩 순회하며 평가를 시작하는 것이다.

 

따라서 선언 시 이터러블의 형태로 존재하던 list는 reduce(add, list)에서 바로 iter의 자리를 차지하며 L.range 함수를 가동시켜 값을 하나씩 평가하고 add 함수를 실행해 나간다.

 

 

 range & L.range Test

 

function test(name, time, f) {
    console.time(name);
    while (time--) f();
    console.timeEnd(name);
}

test('range', 10, () => reduce(add, range(1000000)));
test('L.range', 10, () => reduce(add, L.range(1000000)));

 

이 둘의 성능을 비교하기 위해 간단한 test 코드를 준비했다.

그 결과는 다음의 사진과 같다.

 

 

작은 차이지만 range보다 L.range의 속도가 더 빠른 것을 확인할 수 있다.

 

 

 

 

 

'JavaScript' 카테고리의 다른 글

[FP&ES6+] L.map, L.filter  (0) 2021.12.25
[FP&ES6+] take  (0) 2021.12.25
[FP&ES6+] go, pipe, curry - (3)  (0) 2021.12.11
[FP&ES6+] go, pipe, curry - (2)  (0) 2021.12.10
[FP&ES6+] go, pipe, curry - (1)  (0) 2021.12.10

댓글