관리 메뉴

Bbaktaeho

[Javascript] Generator 활용과 장점 (iterable, iterator, lazy evaluation) 본문

프로그래밍 (Programming)/자바스크립트 (JavaScript)

[Javascript] Generator 활용과 장점 (iterable, iterator, lazy evaluation)

Bbaktaeho 2020. 10. 21. 00:22
반응형

들어가며


generator, iterator, iterable의 상세한 설명은 없습니다.

궁금하신 분들은 아래의 포스팅을 먼저 읽고 보는 것을 추천드립니다.

bbaktaeho-95.tistory.com/79

 

[Javascript] Generator, Iterator, Iterable (제너레이터, 이터레이터, function*)

들어가며 ※ FxJS 라이브러리를 개발하신 유인동 강사님의 함수형 프로그래밍 강의를 듣고 정리한 글임을 밝힙니다. 함수형 프로그래밍 자바스크립트 페이스북 커뮤니티입니다. ko-kr.facebook.com/gr

bbaktaeho-95.tistory.com

무한 루프 (무한수열 만들기)


일반 함수에서 무한 루프가 발생한다면 프로세스가 죽을 수 있습니다.

function infinity() {
  let i = 0;
  while (true) console.log(++i);
}

위 함수에서 우리가 원할 때 증가한 i값을 출력하고 싶어도 그렇게 동작할 수 없습니다.

function infinity() {
  let i = 0;
  while (true) return ++i;
}

console.log(infinity()); // 1
console.log(infinity()); // 1
console.log(infinity()); // 1

꺼내올 때마다 계속 1만 꺼내올 것입니다.

하지만 제너레이터를 활용한다면 증가하는 수를 원할 때 꺼내올 수 있게 됩니다.

 

먼저 제너레이터를 만들어줍니다.

function* infinity() {
  let i = 0;
  while (true) yield ++i;
}

제너레이터는 이터레이터를 만드는 함수로서 이터레이터의 next()를 활용하여 값을 지연 평가할 수 있게 됩니다.

여기서 지연 평가란, 로직에서 뒤늦게 값이 필요할 때 만들어내는 방식입니다.

 

이터레이터를 가지고 무한수열을 만들어보겠습니다.

function* infinity() {
  let i = 0;
  while (true) yield ++i;
}
const iter = infinity();
console.log(iter.next().value); // 1
console.log(iter.next().value); // 2
console.log(iter.next().value); // 3

실행 결과는 1, 2, 3 으로 정상 동작합니다.

마치 데이터베이스의 Auto Increment처럼 우리가 원할 때 iter만 공유한다면 언제든지 1이 증가된 수를 받아서 사용할 수 있게 됩니다.

하지만 스프레드 연산자나 for of 문을 그대로 실행시키면 모두 평가되어 무한 루프가 발생하므로 주의해야 합니다.

지연 평가의 장점


앞서 언급한 지연 평가를 좀 더 확실히 경험해 보겠습니다.

1부터 99까지 순회할 수 있는 반복자를 만들어 보겠습니다.

 

먼저 배열을 만드는 방법입니다.

function newArr(n) {
  let i = 1;
  const res = [];
  while (i < n) res.push(i++);
  return res;
}
const arr = newArr(100);
console.log(arr);

코드를 실행하면

[
   1,  2,  3,   4,  5,  6,  7,  8,  9, 10, 11, 12,
  13, 14, 15,  16, 17, 18, 19, 20, 21, 22, 23, 24,
  25, 26, 27,  28, 29, 30, 31, 32, 33, 34, 35, 36,
  37, 38, 39,  40, 41, 42, 43, 44, 45, 46, 47, 48,
  49, 50, 51,  52, 53, 54, 55, 56, 57, 58, 59, 60,
  61, 62, 63,  64, 65, 66, 67, 68, 69, 70, 71, 72,
  73, 74, 75,  76, 77, 78, 79, 80, 81, 82, 83, 84,
  85, 86, 87,  88, 89, 90, 91, 92, 93, 94, 95, 96,
  97, 98, 99
]

1부터 99까지 배열이 출력됩니다.

 

다음은 이터레이터 즉, 반복 가능한 객체를 만드는 방법입니다. (배열도 반복 가능한 객체입니다)

function* newArrGen(n) {
  let i = 1;
  while (i < n) yield i++;
}
const iter = newArrGen(100);
console.log(iter);

코드를 실행하면 (node.js)

Object [Generator] {}

object 타입이 출력된 것을 확인할 수 있습니다.

다시 말해 반복 가능한 객체가 만들어졌고 즉시 평가되지 않아서 1~99까지 리스트가 만들어지지 않았습니다.

 

이제 위의 두 함수를 가지고 만들어진 반복 가능한 객체로 5의 배수를 작은 수부터 2개만 찾도록 구현해보겠습니다.

function newArr(n) {
  let i = 1;
  const res = [];
  while (i < n) res.push(i++);
  return res;
}
function* newArrGen(n) {
  let i = 1;
  while (i < n) yield i++;
}
function fiveArr(iter) {
  const res = [];
  for (const item of iter) {
    if (item % 5 == 0) res.push(item);
    else if (res.length == 2) break;
  }
  return res;
}

console.log(fiveArr(newArr(100)));
console.log(fiveArr(newArrGen(100)));

실행 결과

[ 5, 10 ]
[ 5, 10 ]

 

같은 결과를 만들어냅니다. 하지만 제너레이터를 활용한 코드는 좀 더 빠르게 동작합니다.

이 차이가 즉시 평가와 지연 평가인데요.

 

fiveArr(newArr(100))

코드는 newArr 함수가 배열을 즉시 만들어냅니다. (1~99까지) 만들어진 배열을 리턴해서 fiveArr 함수를 수행하게 됩니다. 쉽게 말해 fiveArr([1, 2, 3, 4, 5, 6, ..., 97, 98, 99]) 된다는 것입니다.

 

 

fiveArr(newArrGen(100))

코드는 newArrGen 함수가 이터레이터만 만들어내고 fiveArr 함수에서 필요할 때 이터레이터에서 평가된 값을 사용하게 됩니다.

 

극단적으로 크기를 올려서 시간을 비교해보겠습니다.

console.time('');
console.log(fiveArr(newArr(10000000))); // [ 5, 10 ]
console.timeEnd(''); // : 285.535ms
console.time('');
console.log(fiveArr(newArrGen(10000000))); // [ 5, 10 ]
console.timeEnd(''); // : 7.296ms

어마어마한 크기의 반복 가능한 객체를 만든 다음에 fiveArr 함수를 실행하는 모습입니다.

그러나 즉시 평가와 달리 지연 평가는 확실히 빠르게 동작하는 것을 볼 수 있습니다.

 

newArrGen 함수에 log를 찍어보면 필요할 때만 값을 평가해서 동작하는 모습을 볼 수 있습니다.

function* newArrGen(n) {
  let i = 1;
  while (i < n) {
    console.log(i);
    yield i++;
  }
}

...
...
...

console.time('');
console.log(fiveArr(newArrGen(10000000)));
console.timeEnd('');

while문 안에 log를 출력하도록 코드를 수정하고 실행합니다.

1
2
3
4
5
6
7
8
9
10
11
[ 5, 10 ]
: 11.452ms

아무리 큰 수까지 반복해도 결과적으로 필요할 때 평가되어 실행되므로 빠르게 동작할 수 있습니다.

log를 출력하느라 시간이 조금 더 걸린 것 같네요.

 

값이 필요할 때 이터레이터에서 꺼내 쓰므로 무한대로 이터레이터를 만들어도 결과는 같습니다.

console.time('');
console.log(fiveArr(newArrGen(Infinity))); // [ 5, 10 ]
console.timeEnd(''); // : 7.441ms

미완성


추후 더 좋은 예제와 정보로 다시 업데이트 하겠습니다.

반응형