제네레이터와 이터레이터

이효범·2022년 4월 27일
0
post-thumbnail

제네레이터와 이터레이터

제너레이터는 이터레이터이자 이터러블을 생성하는 함수를 말한다.
즉, 이터레이터를 리턴하는 함수이다.

제네레이터는 일반함수에서 앞에 * 을 붙혀줘서 만드며, 이 제네레이터의 실행결과는 이터러블이자 이터레이터이기 때문에 순회를 할 수 있다.

또한 마지막에 return 값을 만들 수 있는데, next를 실행하다가 마지막에 done 이 true 가 되면서 return 에서 전달한 값을 반환해준다.
유의해야 할 것은 순회를 할 떄 리턴값은 별도로 취급하지 않고 순회가 진행된다는 점이다.

# 제네레이터와 이터레이터
  - 제너레이터: 이터레이터이자 이터러블을 생성하는 함수
  
  function *gen() {
     yield 1;
     yield 2;
     yield 3;
     return 100;
  }

  let iter = gen();  // 제네레이터를 실행한 결과는 이터레이터이다.
  console.log(iter.next()); // { value: 1, done: false } 
  console.log(iter.next()); // { value: 2, done: false }
  console.log(iter.next()); // { value: 3, done: false }
  console.log(iter.next()); // { value: 100, done: true }
// 제너레이터를 통해 굉장히 쉽게 이터레이터를 만들 수 있다.

console.log(iser[Symbol.iterator]); // f [Symbol.iterator]() {[native code]}
// 이터레이터이자 이터러블이다.

console.log(iser[Symbol.iterator]() == iter); // true  
// 이터레이터는 심볼 이터레이터를 가지고 있고, 심볼 이터레이터의 실행 결과는 자기 자신이다.
// 즉, 제네레이터는 well-formed 이터레이터를 리턴하는 함수이며, 
// 제네레이터의 yield를 통해서 몇 번의 next 를 통해 값을 꺼내줄 것인지를 정할 수 있다. 

for (const a of gen()) console.log(a); 
// 1 2 3 
---
  
// 제네레이터는 순회할 값을 문장 형식으로 표현하는 것이라고도 말할 수 있다.
function *gen() {
     yield 1;
     if (false) yield 2;
     yield 3;
}
	
let iter = gen();  
console.log(iter.next());  // { value: 1, done: false } 
console.log(iter.next());  // { value: 3, done: false } 
console.log(iter.next());  // { value: undefined, done: true } 
console.log(iter.next());  // { value: undefined, done: true } 

for (const a of gen()) console.log(a); 
// 1 3 

또한 제네레이터는 순회할 값을 문장 형식으로 표현하는 것이라고도 말할 수 있다.
자바스크립트에서는 어떠한 값이든 이터러블이면 순회할 수 있는데,
제네레이터는 이러한 문장을 값으로 만들 수 있고 이 문장을 통해서 순회할 수 있는 값을 만들 수 있기 때문에,
자바스크립트에서 이 제네레이터를 통해 어떠한 상태나 어떠한 값이든 사실상 순회할 수 있게 만들 수 있다는 이야기가 된다.

제네레이터라는 문장을 통해 순회할 수 있는 값을 만들 수 있다는 것은
어떠한 값도 순회할 수 있는 형태로 제네레이터로 조작할 수 있으며,
제네레이터를 통해서 굉장히 다양한 값들을 순회할 수 있는 이터러블을 쉽게 만들 수 있다는 이야기이다.


제네레이터 활용

제네레이터를 활용해서 홀수만 계속해서 발생시키는 이터레이터를 만들어서 순회하는 예제를 만들어보도록 한다.

밑의 코드처럼, 제네레이터를 활용해서 다양한 로직을 만들 수 있다.

# odds

function *odds() {
 yield 1;
 yield 3;
 yield 5;
}
let iter = odds();
console.log(iter.next()); // { value: 1, done: false }
console.log(iter.next()); // { value: 3, done: false }
console.log(iter.next()); // { value: 5, done: false }
console.log(iter.next()); // { value: undefined, done: true }

refactor_1

# odds

function *odds(limit) {
 for (let i = 0; i < limit; i++) {
   if (i % 2) yield i;  // 문장 형식을 통해 어떠한 로직으로 제네레이터를 통해 값을 발생시키는 것을 제어할 수 있다. 
 }
}
let iter = odds(10);
console.log(iter.next()); // { value: 1, done: false }
console.log(iter.next()); // { value: 3, done: false }
console.log(iter.next()); // { value: 5, done: false }
console.log(iter.next()); // { value: 7, done: false }
console.log(iter.next()); // { value: 9, done: false }
console.log(iter.next()); // { value: undefined, done: true }

refactor_2

# odds
function *infinity(i = 0) { // 무한히 값을 생성하지만, 이터레이터의 next를 평가할 때까지만 동작하기 때문에 브라우저가 멈추거나하는 일은 발생하지 않는다.
  while (true) yield i++;
}
let inf_iter = infinity();
console.log(inf_iter.next()); // { value: 0, done: false }
console.log(inf_iter.next()); // { value: 1, done: false }
console.log(inf_iter.next()); // { value: 2, done: false }

function *odds(limit) {
 for (const a of infinity(1)) {
   if (a % 2) yield a;
   if (a === limit) return;
 }
}
let iter = odds(10);
console.log(iter.next()); // { value: 1, done: false }
console.log(iter.next()); // { value: 3, done: false }
console.log(iter.next()); // { value: 5, done: false }
console.log(iter.next()); // { value: 7, done: false }
console.log(iter.next()); // { value: 9, done: false }
console.log(iter.next()); // { value: undefined, done: true }

refactor_3

# odds
function *infinity(i = 0) { 
  while (true) yield i++;
}
function *limit(l, iter) { 
  // 이터러블을 받아서 이터러블 안에 있는 값을 계속해서 yield를 하다가
  // 받아둔 limit과 같은 값을 만나면 더이상 돌지 않도록 동작한다.
  for (const a of iter) {
   yield a;
   if (a === l) return;
 }
}
let iter_limit = limit(4, [1, 2, 3, 4, 5, 6]);
console.log(iter_limit.next()); //  { value: 1, done: false }
console.log(iter_limit.next()); //  { value: 2, done: false }
console.log(iter_limit.next()); //  { value: 3, done: false }
console.log(iter_limit.next()); //  { value: 4, done: false }
console.log(iter_limit.next()); //  { value: undefined, done: true }


function *odds(l) {
 for (const a of limit(l, infinity(1))) {
   if (a % 2) yield a;
 }
}
let iter = odds(10);
console.log(iter.next()); // { value: 1, done: false }
console.log(iter.next()); // { value: 3, done: false }
console.log(iter.next()); // { value: 5, done: false }
console.log(iter.next()); // { value: 7, done: false }
console.log(iter.next()); // { value: 9, done: false }
console.log(iter.next()); // { value: undefined, done: true }

for (const a of odds(40)) console.log(a); 
// 1 3 5 7 9 11 13 ... 37 39

for...of, 전개 연산자, 구조 분해, 나머지 연산자

제네레이터는 이터러블/이터레이터 프로토콜을 따르고 있기 때문에 for...of문이나 전개 연산자, 구조 분해, 나머지 연산자 등, 자바스크립트에서 이터레이터 프로토콜을 따르고 있는 문법들과
혹은 이터어블 프로토콜을 따르고 있는 다양한 라이브러리나 헬퍼 함수들과 함께 사용되어질 수 있다.

따라서 제네리어터나 이터레이터에 대한 활용을 잘할 수 있다면, 좀 더 조합성이 높은 프로그래밍을 해나갈 수 있다.

for...of 문은 위에서 이미 확인을 해봤다.
따라서 전개 연산자와 구조 분해, 그리고 나머지 연산자와 함께 사용되는 사례를 봐보자.

# for...of, 전개 연산자, 구조 분해, 나머지 연산자
console.log(...odds(10)); // 1 3 5 7 9
console.log([...odds(10), ...odds(20)]);
// [1, 3, 5, 7, 9, 1, 3, 5, 7, 9, 11, 13, 15, 17, 19]

const [head, ...tail] = odds(5);
console.log(head); // 1
console.log(tail); // [3, 5]


const [a, b, ...rest] = odds(10);
console.log(a); // 1
console.log(b); // 3
console.log(tail); // [5, 7, 9]
profile
I'm on Wave, I'm on the Vibe.

0개의 댓글