본문 바로가기
개발자 도전기/[STUDY] JS || TS

JavaScript | FP && ES6+ | 제너레이터와 이터레이터

by 답수 2022. 1. 21.
728x90
SMALL

 

 

제너레이터는 이터레이터이자 이터러블이고, 이터러블을 생성하는 함수이다. 이터레이터를 리턴하고, 함수 앞에 *을 붙인다.

function* gen() {
    yield 1;
    yield 2;
    yield 3;
    return 100; // 리턴값은 마지막 done이 true가 될 때 리턴
}

let iter = gen();

console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
/* expected output
{ value: 1, done: false }
{ value: 2, done: false }
{ value: 3, done: false }
{value: 100, done: true}
{ value: undefined, done: true } 
 */

 

흠... 솔직히 저렇게만 설명하면 "그래서 뭐, 어쩌라고?" 라는 반응이 나온다.. (나만 그런가?) 심지어 저 함수 앞에 붙는 * 보면서 C언어 공부할 때 배운 포인터 개념이 떠오르고, 이와는 무관한 개념인지 등 많은 궁금증이 떠오른다... docs보자구..!

 

Generator

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Generator

 

Generator - JavaScript | MDN

Generator 객체는 generator function 으로부터 반환된 값이며 반복자와 반복자 프로토콜을 준수합니다.

developer.mozilla.org

 

제너레이터는 빠져나갔다가 나중에 다시 돌아올 수 있는 함수다. 이때 변수의 값은 출입 과정에서 저장된 상태로 남아 있다.

 

제너레이터 함수는 호출되어도 즉시 실행되지 않고, 대신 함수를 위한 이터레이터 객체를 반환한다. 이터레이터의 next() 메소드를 호출하면 제너레이터 함수가 실행되어 yield문을 만날 때까지 진행하고, 해당 문장이 명시하는 이터레이터로부터 반환값을 반환하다.

 

이후 next() 메소트가 호출되면 진행이 멈췄던 위치에서부터 재실행된다.

 

yield

더보기

yield는 제너레이터 함수를 중지하거나 재개하는데 사용된다.

 

yield는 실질적으로 value와 done으로 이루어진 객체를 반환한다.

 

yield는 제너레이터가 한 번 멈추게 하고 제너레이터의 새로운 값을 반환한다. 그 이후의 next()가 호출된 후, yield 이후의 선언된 코드가 바로 실행된다.

 

 

흠. 그래 오케이! 제너레이터 어떻게 사용하는지 알겠음. 근데, 그래서 왜 사용하는데..? 그냥 이터러블만 사용하면 되는거 아닌감..?

 

그 필요성에 대해서 한 번 나름의 예시를 만들어 보려고 한다.

 

function odds(l) {
    const res = [];
    for (let i = 0; i < l; i++) {
        if (i % 2) res.push(i);
    }
    return res;
}

let iter1 = odds(10);
for (const a of iter1) console.log(a);

일반 함수의 리턴 값은 한 개(혹은 0개)만 존재한다. 위의 odds() 함수는 홀수값을 리턴하는 함수로, 함수 내 res 라는 배열을 만들었고, 그 안에 있는 원소들을 for문을 통해 하나씩 출력했다.

 

function* genOdds(l) {
    for (let i = 0; i < l; i++) {
        if (i % 2) yield i;
    }
}
let iter2 = genOdds(10);
for (const a of iter2) console.log(a);

다음과 같이 제너레이터를 사용하면 yield를 통해 여러 개의 값을 필요에 따라 하나씩 반환할 수 있다.

 

강사님 왈,

자바스크립트에서는 어떠한 값이든 이터러블이면 순회가 가능하다. 제너레이터 문장을 값으로 만들 수 있고, 문장을 통해 순회할 수 있는 값을 만들 수 있기 때문에 어떠한 값이든 순회할 수 있게 만들 수 있다. 이는 굉장히 상징적이고 함수형 프로그래밍 관점에서도 매우 중요한 부분이다.

라고 하신다. 즉 제너레이터는 이터레이터를 더 쉽게 구현할 수 있도록 한다.

 

뭔가 사용만 잘하면 진짜 유용하게 쓸 수 있을 것 같긴 한데... 많은 숙련이 필요할 것 같다..!

 

여하튼! 위의 홀수를 만드는 함수를 아래의 예시처럼 업그레이드도 할 수 있다.

function* infinity(i = 0) {     // 제너레이트 무한 수열도 가능...
    while (true) yield i++;
}

// 이터러블의 제한선을 두는 함수
function* limit(l, iter) {
    for (const a of iter) {
        yield a;
        if (a == l) return;
    }
}

// 홀수 값만 반환하는 함수
function* odds(l) {
    for (const a of limit(l, infinity(1))) {	// 시작 숫자 1로
        if (a % 2) yield a;
    }
}

for (const a of odds(40)) console.log(a);

 

크으... 함수형 프로그래밍 까리하다. 스스로 저렇게 바로 구현할 수 있도록 코드 많이 작성해봐야겠다.

 

 

전개 연산자, 구조 분해 역시 제너레이터로 구현할 수 있다.

// 전개 연산자
console.log(...odds(10));	// expected output: 1 3 5 7 9
console.log([...odds(10)], [...odds(20)]);    
/*
expected output: [ 1, 3, 5, 7, 9 ] [
   1,  3,  5,  7,  9,
  11, 13, 15, 17, 19
]
*/


// 구조 분해
const [head, ...tail] = odds(5);
console.log(head);	// expected output: 1
console.log(tail);	// expected output: [3, 5]

const [a, b, ...rest] = odds(10);
console.log(a);		// expected output: 1
console.log(b);		// expected output: 3
console.log(rest);	// expected output: [5, 7, 9]

 

전개 연산자

 

 

 

728x90
LIST

댓글