LHJ

I'm a FE developer.

16. iterable 객체 연습 - Generator

10 Aug 2020 » codespitz_re

iterable 객체 연습 - Generator

iterable, iterator interface 소스를 줄여쓰는 방법, 그것이 Generator첫번째 임무이다.
Generator는 세가지 용법이 있다.
이번 시간에 배울 용법은 제일 쉬운 iterator Generator 용법이다.

iterator Generator 용법

iterator가 너무 기니깐 그걸 좀 더 짧고 쉽게 만드는 기능을 제공해준다.

const N2 = class {
    constructor(max) {
        // max 설정 이유 : 이 배열이 무한배열이 되는걸 막아주기 위해 만든 것
        this.max = max;
    }
    [Symbol.iterator](){
        let cursor = 0, max = this.max;
        return {
            done: false,
            // next 함수 태어나는 시점
            // 태어나는 시점에서 위의 curser, max같은 자율변수를 캡쳐에 가둘 수 있다.
            // 우리가 흔히 아는 클로져라는 개념이다.
            next() {
                if (cursor > max) {
                    this.done = true;
                }else {
                    this.value = cursor * cursor;
                    cursor++;
                }   
                return this;
            }
        }
    }
}

위의 코드를 Generator를 활용하면 아래처럼 짧게 만들 수 있다.

const generator = function* (max) {
    let cursor = 0;
    while(cursor < max) {
        yield cursor * cursor;
        cursor++;
    }
}

function 뒤에 공간없이 *를 붙여아 Generator로 인식한다.
Generator를 만드는 리터럴이라고 생각하면된다.
GeneratorGenerator 함수를 호출할 때마다 iterator가 만들어진다.
그런데 Generator가 만드는 iterator는 동시에 iterable이기도 하다.

배열을 for … of 문으로 돌리면 돌아간다.
여기서 알 수 있는 것은 배열은 iterable 이라는 것이다.
배열은 iterable 이면서 동시에 Symbor.iterator를 호출해보면 배열이 나온다.
배열 자체가 동시에 iterator 이기도 한 것이다.

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

arr[Symbol.iterator]();
// Array Iterator{}

iterator 이자 iterator result object, 배열은 iterable 이자 iterator result object.

Generator 를 for … of 문으로 돌릴 수는 없다.
Generator 안에 반복문이 들어가있기 때문..이라고 추측
이전에 말했던 언어적 혜택은 iterable 만을 받아주기로 했다.

  • Generator 가 하는 일 - iterator 객체 만들기
  • Iterable 이 하는 일 - iterator 객체 만들기

그런데 for … of 뒤에 iterable 은 와도 Generator 는 못온다.
iterable 이자 iterator 객체여야 for … of 로 돌릴 수 있다는 거다.

const generator = function* (max) {
    let cursor = 0;
    while (cursor < max) {
        yield cursor * cursor;
        cursor++;
    }
}

const iter = generator(5);

for (const v of iter) console.log(v);

// 0
// 1
// 4
// 9
// 16

Generator 자체는 for … of 문으로 못돌리지만 next를 반환한 iterable 객체는 돌릴 수 있다.
Generator 함수 안에서만 쓸 수 있는 yield 키워드란?

  • yield 효과 = iterablenext() 반환효과

Suspension

yield에서 Suspension 이라는 일이 생긴다.
Suspension 이라는 일이 생기면서 이때 잠깐 iterator result object를 반환하게 된다.
yield cursor * cursor 값과 done 까지 객체형태로 바깥으로 내보내고 정지한다.
**바깥쪽에서 next를 호출할 때까지 정지한다.

이것이 얼마나 대단한 일인지 아직 감이 안올 것이다.
우리가 여지껏 배웠던 폰노이만 구조에서 while 문을 정지시킬 수 있는 방법이 있었나?
없었다.
그런데 Generator는 while 문이 돌다가 yield 부분에서 멈춘다.
그리고 그 다음에 그 아래에 있는 cursor++부터 다시 실행된다.
이것이 바로 suspension 기능이다.

자바스크립트는 문을 만들 때 문 하나를 Record로 만들고 Special Record를 이용해서 제어문을 반환한다.
그럼 위의 Generator 함수 안의 while 문도 다 Record로 이루어져 있겠지?
따라서 자바스크립트 엔진 실행기는 원래 레코드를 돌리고 있고 진짜 노이만 머신이 아니라 노이만 머신을 에뮬레이팅(모방)하고 있다는 것이다.

노이만 머신은 이들을 진짜 메모리에 적재시켜서 돌아가게 하지만 자바스크립트 엔진은 얘네들을 전부 Record 로 만들었기 때문에 Record 를 돌려주고 있는 가상머신을 돌리고 있다.
그런데 여기서 yield를 하면 가상머신을 돌리다가 Record 를 더 이상 실행 안하고 멈춘다.

이것을 suspension 이라고 한다.
요즘은 suspension을 제공해주는 언어가 꽤 많다.
자바는 안되지만 코틀린은 되고 C#과 파이썬도 된다.

Suspension 을 알기 전까지 상식

suspension을 알기 전까지는 ‘문’은 절대로 멈출 수 없는 존재였다.
그런데 멈출 수 있게 되었다.
멈출 때마다 바깥에 iterator result object를 return한다.
cursor * cursorvalue로 삼고 donefalse로 삼아 바깥으로 계속 return해준다는 것이다.

코루틴

우리는 똑같은 문장을 여러번 시킬 수 있는 함수 블록을 전문적인 용어로 루틴이라고 부른다.
여러번 반복하기 때문에 루틴이다.
그런데 Generator는 루틴이 아니다.

루틴은 한번 들어와서 한번에 쭉- 실행되고 끝나는데, Generator는 한번 들어가고 여러번 나갔다가 여러번 다시 들어갈 수 있다.
이걸 코루틴이라고 부른다.

  • Generator 학술적 용어 : 코루틴
  • 함수 학술적 용어 : 루틴
const generator = function* (max) {
    let cursor = 0;
    while (cursor < max) {
        yield cursor * cursor;
        cursor++;
    }
}

const iter = generator(5);

iter.next(); // {value: 0, done: false}
iter.next(); // {value: 1, done: false}
iter.next(); // {value: 4, done: false}
iter.next(); // {value: 9, done: false}
iter.next(); // {value: 16, done: false}
iter.next(); // {value: undefined, done: true}