관리 메뉴

Bbaktaeho

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

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

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

Bbaktaeho 2020. 10. 17. 01:05
반응형

들어가며


FxJS 라이브러리를 개발하신 유인동 강사님의 함수형 프로그래밍 강의를 듣고 정리한 글임을 밝힙니다.

 

함수형 프로그래밍 자바스크립트 페이스북 커뮤니티입니다.

ko-kr.facebook.com/groups/539983619537858/

 

Facebook에 로그인 | Facebook

메뉴를 열려면 alt + / 키 조합을 누르세요

ko-kr.facebook.com

iterable, iterator


iterable : 반복 가능한 / iterator : 반복자

자바스크립트에서 배열은 반복 가능한 객체(object)입니다. 반복이 가능하다는 것은 iterable이라고 부르기도 합니다.

자바스크립트는 itrable 요소들만 순회할 수 있는 문법이 있습니다.

for of 반복문인데요. 이 반복문은 iterable 요소들만 반복할 수 있고 그렇지 않은 요소들로 반복하려고 할 때

TypeError: 요소 is not iterable 에러를 던집니다. (for in 반복문은 일반 객체도 순회합니다!)

const test = { a: 1, b: 2 };

for (item of test) {
  console.log(item);
}

// TypeError: test is not iterable

좀 더 상세하게 설명하자면

iterableiterator를 리턴하는 [Symbol.iterator]() 를 가진 값,

iterator{value, done} 객체를 리턴하는 next() 를 가진 값입니다.

value는 값으로서 여러 타입이 올 수 있고 done은 boolean 타입을 가집니다.

const arr = [1, 2, 3, 4];
const set = new Set([1, 2, 3, 3, 5, 2]);
const map = new Map([
  ['a', 'A'],
  ['b', 'B'],
]);
const obj = { a: 1, b: 2 };

console.log(arr[Symbol.iterator]());
console.log(set[Symbol.iterator]());
console.log(map[Symbol.iterator]());
console.log(obj[Symbol.iterator]());

모든 자바스크립트 코드는 node로 실행하겠습니다.

위 코드를 실행하면

Object [Array Iterator] {}
[Set Iterator] { 1, 2, 3, 5 }
[Map Entries] { [ 'a', 'A' ], [ 'b', 'B' ] }
console.log(obj[Symbol.iterator]());
                                ^

TypeError: obj[Symbol.iterator] is not a function

console.log(obj[Symbol.iterator]()); 코드에서 에러가 발생합니다.

즉, 일반적인 객체는 iterable이 아닌 것을 알 수 있습니다.

generator


generator : 발전기, 발생기

자바스크립트에는 generator가 있습니다.

자바스크립트에서 generator는 이터레이터를 발생시키는 함수로 사용됩니다.

구현은 function* 키워드를 사용해서 코드를 구현합니다.

function* gen() {}
// generator 함수로 이름은 gen이며 이터러블을 생성하는 함수

제너레이터 함수 내부에 yield 키워드를 사용해서 이터러블의 요소를 표현식으로 나타낼 수 있습니다.

function* gen() {
  yield 1;
  yield 2;
  yield 3;
  return -1;
}

const iter = gen();
for (item of iter) console.log(item);

위 코드를 실행시키면

1
2
3

마치 배열을 순회해서 요소들을 출력한 것처럼 로그에 출력됩니다.

yield 키워드가 이터러블의 요소가 되는 것을 볼 수 있습니다. 쉽게 말해서 마치 배열처럼 yield 옆에 표현식이 배열의 요소가 되는 거라고 생각해볼 수 있습니다. (실제 배열은 아님)

그럼 return은 어디에서 확인할 수 있는지 궁금할 수 있습니다.

 

return 된 값은 어디로 갔는지 알아보기 전에 먼저 [Symbol.iterator]() 부터 짚고 가겠습니다.

Symbol를 설명하면 글이 길어질 것 같아서 간단하게 설명하자면 객체의 속성을 만들 수 있는 데이터 타입으로 이해하면 됩니다.

그중, Symbol.iterator는 객체의 iterble 속성을 정의하기 위해 쓰입니다.

직접 일반 객체를 iterable 객체로 구현할 수도 있습니다.

const iterable = {
    [Symbol.iterator]() {
        let i = 3;
        return {
            next: () =>  i == 0 ? {value: undefined, done: true} : {value: i--, done: false},
            [Symbol.iterator]() {return this}
        }
    }
}
const it = iterable[Symbol.iterator]();
for(let i of it) console.log(i);

코드를 실행하면

3
2
1

순서대로 3, 2, 1이 콘솔에 출력됩니다.

Symbol.iterator를 객체의 속성으로 지정하고 함수로 사용하는 모습입니다.

이 함수는 객체를 리턴하며 리턴된 객체는

{
  next: [Function: next],
  [Symbol(Symbol.iterator)]: [Function: [Symbol.iterator]]
}

위와 같은 모습을 가지고 있습니다.

여기서 next 함수를 통해 {value, done} 객체를 가져오게 됩니다. 즉, iterator라고 할 수 있습니다(위에 설명).

 

다시 돌아와서 제너레이터의 return 값은 done이 true일 때 알 수 있습니다.

for of 반복문으로 순회하기 전에 제너레이터는 이터레이터니까 next() 함수를 실행해서 결과를 확인하겠습니다.

function* gen() {
    yield 1;
    yield 2;
    yield 3;
    return -1;
  }
  
  const iter = gen();
  console.log(iter.next());
  console.log(iter.next());
  console.log(iter.next());
  console.log(iter.next());
  console.log(iter.next());
  console.log(iter.next());

코드를 실행하면

{ value: 1, done: false }
{ value: 2, done: false }
{ value: 3, done: false }
{ value: -1, done: true }
{ value: undefined, done: true }
{ value: undefined, done: true }

{value, done} 객체가 출력됩니다.

여기서 유추할 수 있는 게 for of 반복문으로 이터러블 객체를 순회했을 때 done이 false일 때의 value 값을 순회한 것을 알 수 있고 done이 true일 때는 return 받은 값이 value에 할당된 것을 볼 수 있습니다. return이 되면 이후 value 값은 undefined 값으로 할당되네요.

 

실제 for of 반복문을 일반 반복문으로 변경한다면

const arr = [1, 2, 3, 4, 5, 6, 7];

// for of
for (const item of arr) {
  console.log(item);
}

// for of 동작 과정
const iter = arr[Symbol.iterator]();
let cur;
while (!(cur = iter.next()).done) {
  const item = cur.value;
  console.log(item);
}

이터레이터의 next 함수를 실행시켜서 value 값을 추출하는 것으로 나타낼 수 있습니다.

 

 

전개 연산자(...)를 사용해서 done이 false일 때 value 값들을 전개할 수도 있습니다.

console.log(...iter);
// 1 2 3

여기까지 본다면 왜 제너레이터를 사용하는지 알기 어렵습니다.

다음 포스팅에서 제너레이터를 이용해서 얻는 장점에 대해서 글을 쓰도록 하겠습니다.

반응형