3. iterator generator 공부
자바스크립트 인터페이스란?
// 키 : function(){}
// function과 콜론은 생략 가능
{
test(str){return true;}
}
인터페이스가 제시하는 조건대로만 만들면 된다.
Symbol - ES6 에 추가된 primitive 타입. 값이라는 뜻. 객체가 아니다.
typeof로 찍으면 symbol 반환
Symbol은 primitive 값이긴 하지만 객체의 키로 사용할 수 있다는 특징이 있다.
대괄호를 통해 정의 또는 호출한다.
let a = {}
a[Symbol];
이터레이터만 있으면 되는 거 아니야? 왜 이터러블 객체가 필요하지?
위 이터레이트를 다시 보면, pop으로 배열을 한번만 다 빼내면 다시는 못씀.
데이터의 원본도 사라짐.
하지만 위 이터레이터가 데이터의 사본을 만들고 데이터의 사본을 가지고 반복문을 돈다고 생각하면, 리셋만 하면 얼마든지 여러번 돌 수 있겠죠?
그래서 루프를 다시 돌 수 있는 리셋타이밍이 필요해.
그것이 바로 이터러블. 어터러블에게 이터레이터 객체를 요청할 때 이터레이터 객체를 리셋하거나 다시 만들어줄 찬스가 생기는 것.
그래서 여러번 루프를 돌 때마다 루프를 위한 변수와 원본 데이터를 구분해서 이터레이터를 잘 구축하라고 이터러블이 한번 개입하는 것
이터러블이 이터레이터를 한번 요청하는 행위에서 새로운 이터레이터 객체를 만들던지, 아니면 데이터를 리셋할 기회를 주던지.
이렇게 하면 루프를 여러번 돌려도 객체가 안 깨지기 때문에 이걸 구현하라고 이터러블을 통해서 이터레이터 객체를 얻게 되어있다.
이런 패턴은 거의 모든 언어에 있다.
디자인 패턴의 한 종류
이터레이터 패턴을 준수하는 언어는 대부분 이런 게 되고, 이거를 사용하는 특별한 루프문을 제공해준다.
그러면 우리는 왜 이터레이터를 쓰지? 우리에게는 멋있는 abc 언어인 for, while, do while이 있는데 왜 이런걸 쓰는거야?
- statement : 문 - 엔진한테 주는 힌트. 실행한번되면 흔적도 없이 사라진다.
- expression : 값 - 메모리에 남는다. 따라서 언제든지 조회할 수 있고 참조할 수 있다.
즉, for, while, do..while 문은 ‘문(statement)’이기 때문에 흔적도 없이 사라진다.
두 번 다시 이 ‘문’을 반복시킬 수 없다.
두 번 다시 쓸 수 없기 때문에 또 반복’문’을 작성하던지, 아니면 함수로 만들어서 다시 호출하던지, 이 방법밖엔 없다.
그래서 우리는 루프를 ‘식’으로 바꾸고 싶다.
왜?
여러번 재현하거나, 중간에 멈추거나 아니면, 이 루프라는 행위 자체를 객체화 시킬 수 있기 때문이다.
현대 언어의 중요한 패러다임은 ‘문’을 제거하고 죄다 ‘식’으로 바꾸는 것이다.
기존에 ‘문’만이 할 수 있었던 것들을 ‘식’, ‘값’으로 바꾸려고 한다.
그렇다면, 모든 ‘문’을 ‘식(값)’으로 바꾸기 위해 죄다 함수에 집어넣어버리면 우리는 함수를 값으로 잡을 수 있기 때문에 해당 함수를 호출하면 실행되겠지?
그러면 여러번 그 ‘문’을 반복해서 실행할 수 있네?
원래 ‘문’은 메모리에 적재돼서 한번 실행되고 나면 폰노이만 머신 모델에서 말했듯이 메모리에서 사라진다.
하지만 함수에 담아놓으면 메모리에서 사라지지 않기 때문에 몇번이고 다시 실행할 수 있다.
기본적인 FLOW 흐름에 따라 실행하는 것이 아닌 내가 원할 때 아무때나 불러서 실행할 수 있다.
이러한 기저에서 보면 현대 언어는 ‘문’을 죄다 ‘식(값)’으로 바꾸려고 한다.
디자인 패턴 깊게 공부하시면 커멘드 패턴이라는 게 있는데 이 패턴이 우리가 생각하는 ‘문’을 ‘값’으로 바꿔서 우리가 마음대로 호출할 수 있게 해주는 패턴이다.
이러한 철학 하에서 보면, for문과 while문을 식으로 바꾸고 싶어.
그런데 얘네는 특수한 경우니까 커멘드 패턴추룸 일반적인 객체로 바꿀 필요는 없고 (사실 커멘트 패턴으로 이터레이터 대체 가능)
하지만 거기까지 갈 필요 없고 반복 전용에 해당되는 객체로만 바꿔주면 될 듯.
이것이 바로 이터레이터 패턴
이터레이터 객체와 next 객체가 반복 자체를 하지는 않는다. 반복 자체를 하진 않지만, 외부에서 이를 이용해 반복하려고 하면, 이제는 위 iterator interface 안에 반복을 할지말지 내용도 들어있고 반복했을 때 어떤일을 해야할지의 내용도 위의 iterator interface 안에 들어있다.
이를 Self Description이라고 부른다.
내가 내 자신을 설명하는 것.
위에서는 내가 내 자신을 설명하지 못하고 문에 있는 내용이 나를 설명한다.
‘문’이 루프를 어떻게 돌릴지를 알고 있는 것.
하지만, 위의 iterator interface는 나 자신이 루프를 얼만큼 돌릴지를 알고 있다.
따라서 왼쪽의 제어문은 자기 주도 하에 자기가 적극적으로 루프를 돌리고 있는데,
iterator interface에서는 반대로 스스로가 주도권을 갖고 있으니까, 반대로
iterator interface에 의존한 루프밖에 못돌린다.
더 반복할지 말지는 iterator interface가 결정한다.
즉, while, for 등 반복’문’은 소극적으로 변할 수밖에 없다.
이러한 반복문들은 next만 호출할 수 있다.
심지어 더 반복될지 안될지도 모른다.
그건 iterator interface의 next 안의 value가 결정. 이러한 차이 때문에 while, for ‘문’은 한번 실행되고 말지만 iterator interface는 self description을 통해서 몇번이고 실행할 수 있는 것이다.
즉, 반복을 직접하진 않지만, 외부에서 반복을 실행하려할 때 반복을 하기 위한 조건과 실행을 iterator iterface 객체가 미리 갖고 있는 것이다.
이것이 iterator result object 이다.
반복 행위와 반복을 위한 준비를 분리시키다.
- 반복행위 - 반복문
- 반복을 위한 준비 - 이터레이터 객체
분리시켰기 때문에 위에서도 말했지만 반복 행위를 몇번이고 반복할 수 있다.
행위는 next만 호출해주면 된다.
- 여러분들이
for
문을 두번 쓰는데i
값을 제대로 맞췄을까?
for
문을 두번 쓸 때 똑같은for
문을 두 번 작성할 수 있는 사람은 드물다.
구구단 같이 쉬운 건 두 번 돌리겠지.
어려운 거는?
하지만 iterator interface 객체라면, 몇번이고 어려운 반복문을 실행시킬 수 있다.
이것이 iterator 객체를 사용하려는 이유다.
‘문’을 사용하지 않고 ‘식(값)’을 사용하려는 이유.
폰 노이만 머신 구조에 따라 ‘문’은 한 번 실행되고 메모리에서 사라진다.
사람이 똑같은for
문을 두번 만드는 것은 쉽지 않다.
구구단 처럼 쉬운 루프면 몰라도.
개발자들은 루프’문’을 쓰는 거 자체를 문제라고 본다는 뜻이다.
사용자 방법 처리기를 통해 iterator를 소비하는 훈련
iterator 객체 : 반복을 위한 준비
즉, 행위와 상태를 분리함.
즉, 우리가 행위 쪽도 짜보자는 이야기
사용자 반복 처리기, 직접 iterator 반복처리기를 구현
// 첫번째 인자값 : 이터러블 객체
// 두번째 인자값 : 이 이터러블 객체가 반복될 때마다 해야될 일을 함수 f로 받는다.
const loop = (iter, f) => {
// 우선 이터러블인지 아닌지를 검증
// Symbol.iterator가 function인지를 검사, 그리고 이 함수를 불러서 return 받는게 이터레이터 객체
// 이것이 이터러블의 정의
// 이전에는 이것이 동적 데이터를 함부로 검사한 것처럼 보였을 것이다.
// 하지만 지금은 자바스크립트 공식 인터페이스인 이터러블을 약식으로 검증했구나. 라고 보여야됨.
// 그리고 iter도 검증을 해줘야된다. iterator result object인지 검증 필요. 어떻게?
// in으로 value와 done이 있는지 검증해야된다. 여기까지는 귀찮아서 안함.
// 어쨌든 이는 단순히 런타임 값 검사가 아니라 자바스크립트 공식 인터페이스인지 아닌지를 검증하는 것으로 봐야된다.
if (typeof iter[Symbol.iterator] == 'function') {
// 이터러블 인터페이스를 통해서 이터레이터를 얻었다.
iter = iter[Symbol.iterator]();
}else return;
// iterator result object가 아니라면 건너뜀
if (typeof iter.next != 'function') return;
// 위 과정을 통과하면 드디어 루프를 돈다.
// 아래 루프처리기는 아무 일도 못함. 그냥 돌린다라는 행위말고는 아무 것도 못함.
// 그렇기 때문에 while 안에 true가 들어감. 무한루프를 돔.
// 기본적으로 아래 루프문은 반복말고는 다른걸 할 생각이 없는 것이다.
// 아래 while은 단순히 반복기일 뿐. 아무런 권한과 책임이 없다.
// 이건 재귀함수로 짜도 상관없다. next의 결과값 done이 true일 때까지만 도는 재귀함수로 짜도 상관 없다는 것.
do{
const v = iter.next();
if (v.done) return; // 종료처리
f(v.value); // done이 false라면 현재 값을 전달함
}while(true)
}
반복될 때의 조건에 해당하는 값들과, 반복기를 분리했더니
반복기 쪽에서는 그냥 돌리기만하면 되는, 즉, 책임이 확 줄고,
나머지 상태관리나 루프에 대한 책임은 모두 이터레이터 객체가 가져가 버렸다.
이런식으로 짜면 이터레이터 객체는 몇번이고 루프를 돌아도 안정적으로 성공할 것이다.
여러분은 더 이상 어려운 루프를 짜지 않아도 된다는 것이다.
const loop = (iter, f) => {
if (typeof iter[Symbol.iterator] == 'function') {
iter = iter[Symbol.iterator]();
}else return;
if (typeof iter.next != 'function') return;
do{
const v = iter.next();
if (v.done) return;
f(v.value);
}while(true)
}
const iter = {
arr: [1, 2, 3, 4],
[Symbol.iterator]() {return this;},
next() {
return {
done: this.arr.length == 0,
value: this.arr.pop()
}
}
};
loop(iter, console.log);
이것을 이터레이터 패턴이라고 한다.
이런 이터레이터 패턴을 구현하는 데 있어서, 자바스크립트 표준 스펙이 나오고 이걸 구현하는 공식적인 방법이 스펙으로 정리되어있을 뿐입니다.
여러분들이 여태 나름대로의 이터레이터를 구현해왔다면 앞으로는 자바스크립트 표준 스펙이 맞춰 이터레이터를 구현해야 된다.
- 이유 : 언어의 혜택이 굉장히 많기 때문에
위의 루프를 간단하게 만든 사용자 루프 처리기라고 생각합시다.
그럼 언어의 혜택을 받는다는 것은 무슨 말인지 살펴보자.
언어의 혜택 : 내장반복처리기, Array destructuring(배열 해체)
단지 이터레이터의 스펙만 충족해주면 이러한 기능들을 사용할 수 있다는 것이다.
- 첫번째로 배열 해체라는 것이 있습니다.
Array destruction이라고 불립니다.
해체라고 이야기하기도 하고, 분열이라고 이야기하기도 하고, 비구조화라고도 이야기합니다.
이는 한국말로 적당한 말이 없어서 그런 것.
- 아래는 이터러블 객체 : iterator result object 를 반환한다.
const iter = {
[Symbol.iterator]() {return this;},
arr: [1, 2, 3, 4],
next() {
return {
done: this.arr.length == 0,
value: this.arr.pop()
};
}
};
// 해체 구문은 일반적으로 '할당' 즉, 변수를 선언하는 쪽에 쓰인다.
// 아래 a는 이 배열로부터 첫번째껄 얻게 된다.
// 나머지를 모아서 b에 담아라.
// 위 iterable interface 구문을 지켰을 때 아래 iter를 가상의 배열로 볼 수 있다. next()를 실행...
// 언제까지 next()를 실행? done이 true가 될 때까지..
const [a, ...b] = iter;
console.log(a, b)
iter는 배열도 아니었다.
하지만 여러분들이 iterable interface의 요구 스펙만 지켜주면, 언어의 혜택을 바로 받을 수 있게된다.
왜냐면 처음부터 이 언어에 해체라는 기능은 이터러블에 반응하는 거지 배열에 반응하는 것이 아니다.
- 이터러블을 이용해야되는 이유가 늘었다.
언어의 혜택을 받기 위해서
언어의 혜택 : 내장반복처리기, Spread(펼치기)
const iter = {
[Symbol.iterator]() {return this;},
arr: [1, 2, 3, 4],
next() {
return {
done: this.arr.length == 0,
value: this.arr.pop()
};
}
};
// iter 값을 펼쳐서 배열에 넣어버림
// 그 배열을 a 상수에 담음
// 아까와는 반대로 값을 펼쳐서 배열에 담는다.
// 즉, 이는 해체가 아니라 스프레드라고 부른다.
// 이런 스프레드(펼치기)도 이터러블에 반응하는 것.
// 이터러블 객체가 아니면 작동하지 않는다. 반드시 이터러블 객체여야 된다.
const a = [...iter];
console.log(a)
언어의 혜택 : 내장반복처리기, Rest Parameter(나머지 인자)
const iter = {
[Symbol.iterator]() {return this;},
arr: [1, 2, 3, 4],
next() {
return {
done: this.arr.length == 0,
value: this.arr.pop()
};
}
};
// 함수의 인자도 펼칠 수 있다.
// 이것은 '값'을 펼친 것도 아니고 해체를 한 것도 아니다. 함수의 인자를 펼친 것.
const test = (...arg) => console.log(arg);
test(...iter);
우리가 귀찮음을 무릅쓰고 이터레이터 객체로 만든다면, 이러한 언어적 혜택을 다 받을 수 있다.
이터레이터, 이터러블 객체만들 땐 좀 괴로울지 몰라도, 사용할 때는 엄청 예뻐지는 것.
자바스크립트 ES6 이후부터는 반복을 위하여 이터러블을 만든다.
for … of 구문
이터레이터를 소모하는 구문으로 for … of 라는 정식 구문이 생겼다.
이 문은 아까 위의 while(true) 처럼 반복에만 집중해주는 문이라 할 수 있다.
기존의 for 문과는 다르다.
for … of 문은 권한이 별로 없는 문이라 할 수 있다. while(true) 처럼.
즉, 아까 위의 loop 같은 함수를 만들 필요가 없다. 사실. for… of 문이 있기 때문에
const iter = {
[Symbol.iterator](){return this;},
arr: [1, 2, 3, 4],
next(){
return {
done: this.arr.length == 0,
value: this.arr.pop()
}
}
}
// for of 문은 done은 알아서 처리하고, value만 따로 받아 처리할 수 있다.
for (const v of iter) {
console.log(v);
}
그렇다면 자바스크립트 내장 객체 중에 이터러블 객체는 무엇이 있을까?
- 배열
- 스트링 (for… of 문에 사용할 수 있다)
스트링 스펙을 보면 이터러블 객체라고 되어있다.
때문에 for… of 문이나 위에서 말한 해체, 할당 등을 할 수 있다.
그래서 substr 같은 걸 안 써도 손쉽게 분해할 수 있다.
split 사용해서 쪼갰지만, 지금은 그냥 분할해도 된다.
신규로 출시되는 많은 API들도 이터러블을 끼고 태어나기 때문에 이터러블 인터페이스는 ES6+ 세계에선 반드시 이해하고 외우고 있어야 하는 내용이다.
Practice
이제까지는 그래도 이터러블 객체 안에 정적의 Array가 있는 상태였다.
이제는 그것이 아닌 가상의 Array를 다루는 알고리즘을 만들어 보자.
클래스 구문을 만들 땐 생성자를 constructor
라는 특별한 키로 선언하게 되어있다.
이 키는 예약어이다.
const N2 = class {
constructor(max) {
// max 설정 이유 : 이 배열이 무한배열이 되는걸 막아주기 위해 만든 것
this.max = max;
}
}
우리는 프로그램이 노이만 머신 모델에 의하면 메모리에 적재되면 쉴틈없이 실행되고 우리는 그 사이에 간섭하지 못한다는 것을 알고 있다.
쉴틈없이 일하는 동안 우리가 아무 간섭도 하지못하는 이 상태를 뭐라고 부르냐면 동기 명령이라고 부른다.
동기 명령이란 한번에 적재된 명령이 한번에 쭉~ 실행되는 것을 말한다. 이런 동기 명령이 실행되는 걸 관찰하는게 FLOW이다.
동기 명령이 실행되는 동안엔 우리가 간섭할 수 없다. CPU를 건들 수가 없다.
그래서 우리가 이 CPU를 못 건드리는 이 현상, 이것을 블로킹이라고 부른다.
블록, 논블록은 바로 여기서 오는 말이다.
- 동기적인 명령어가 실행되는 동안에 우리가 꼼짝도 못하는 걸 블로킹당했다라고 말한다.
그러면 블로킹을 당한 동안엔 해당 적재된 프로그램들이 CPU를 독점적으로 사용하고 있는 상황이다.
그럼 그 동안 그래픽처리, 네트워크 처리 등등 아무것도 못한다는 소리잖아요?
그럼 브라우저는 몇초동안 동기적인 명령처리를 허용해줄까?
브라우저의 동기적 명령처리 허용 시간?
허용시간에 대한 개념이 없으면 루프를 과도하게 돌려 블로킹을 엄청 일으키는 소스를 짤 것.
브라우저는 과연 어느정도까지 여러분들이 블로킹하는걸 봐주고 있을까?
PC용 브라우저는 보통 30초 정도 봐준다고 알려져있지만,
계속된 브라우저 업데이트로 요즘은 20초도 안봐준다.
모바일에선?
크롬 같은 경우는 크로니움 소스가 안드로이드와 완전히 동일하기 때문에 안드로이드에 있는 크롬과 PC에 있는 크롬 소스가 완전히 똑같다.
그렇기 때문에 얘도 똑같이 20초 적용을 받을 것이다.
그러나 안드로이드 OS는 5초 이상의 블로킹이되면 앱을 중지시키는 경향이 있다.
브라우저에서 20초를 허용해도 소용없는 것.
마찬가지로 window 10은 제어권을 윈도우 OS가 들고 있다.
윈도우 OS도 15초를 넘기면 마음에 안든다고 죽여버리는 기능이 있다.
여러분들이 블로킹을 걸 수 있는 범위는 5초 이내라고 보는 게 정상이다.
문제는 5초동안 블로킹 당하는 건 시간인데, 그 시간동안 얼만큼 많이 일할 수 있나는 뭐에 달려있지?
여러분들 CPU 클럭에 달려있다.
클럭이 많으면 더 많은 일을, 적으면 더 적은일을 한다.
또한 CPU 비트 수에 달려있다.
64비트는 8바이트씩 처리해서 더 많은 일을, 32비트느 4바이트씩 받아서 더 적은 일을 하게될 것.
즉, 우리가 루프를 몇번 걸었냐에 따라 시간이 얼마나 걸릴지를 알 수 없다는 것이다. 머신마다 처리능력이 다르기 때문에.
즉, 1억번 루프를 하더라도 어떤건 0.5초만에, 어떤 CPU는 5분만에 처리할 수도 있다.
그러기 때문에 루프가 길어지면 블로킹으로 걸면 안됨.
이번 블로킹에 놔줘야됨.
어떻게?
총 1억번 루프 -> 1000번루프 * 10만번
하지만 루프가 끝나고 다시 실행한다면 여전히 블로킹임. 다를게 없음.
그렇기 때문에 사이사이마다 ‘슬립’을 걸어줘야 된다.
CPU를 다른 애들이 사용할 수 있게 풀어줘야된다는 것.
자바스크립트는 이런 행위를 ‘프레임’이란 개념으로 할 수 있다.
- 노드 - 넥스트팁
- 브라우저 - 리퀘스트애니메이션프레임, 셋타임아웃
자바스크립트만 이런 긴 블로킹을 못 갖는게 아니다.
자바고 C고 나발이고 없다. OS가 더 강하기 때문에 OS가 바로 잘라버린다. 야, 이렇게 블로킹하면 안돼!
왜 이런 기조로 바뀌냐면, 안드로이드가 게임을 개발했는데, 이 게임이 데이터처리한다고 5분을 붙잡고 있어.
근데 그 사이에 전화왔어. 근데 데이터처리한다고 전화를 못받아.
이걸 허용할 수 있냐는 거야. 안되지?
전화오면 무조건 죽여야된단 말야. OS가 이겨야된단 말이지.
그래서 요즘엔 프로세스의 권한을 축소해서 마음대로 할 수 있는 OS가 탄생하는 것이다.
그래서 윈도우든 IOS든 안드로이드든 센 권한을 갖고 있는 것.
옛날에 바이너리 C코드들은 거의 머신 자원을 자기네들이 독점한단 말야.
얘가 죽으면? 블루스크린 뜨는거야.
옛날엔 OS를 죽일 정도로 앱이 강력한 권한을 가졌었는데,
지금은 앱만죽지 안드로이드 OS가 통채로 뻗진 않는다.
OS가 더 많은 권한을 갖고 있어서 그렇다.
이러한 기저에 있어서 우리는 루프를 함부로 길게 만들 수 없다.
그런데 궁금한게 루프가 그렇게 긴게 있나요? 그렇게 걱정할 정도로?
canvas 태그 생각해봐라.
100X100 이미지를 그레이스케일 씌워주려고 canvas 태그 활용해 반복문만 돌려도 벌써 10000번이다.
100X100은 그리 크지도 않은 아이콘 사이즈이다.
100X100은 그렇다치자.
1920X1000 이런이미지는?
그냥 루프돌다가 브라우저가 야 돌지마! 죽여버리는 것.
성능이 나쁜 PC에서하면 OS가 브라우저 채로 죽여버림.
그렇기 때문에 루프를 분산해서 거는 스킬이 필요하다.
루프를 만들 때 좋은 방법. 무조건 limit를 걸어라.
while(true) 가 아닌 while(true && i<7) , i는 바깥에다 정의하고..
이런 식으로..
우리가 만드는 루프에는 이런 안전장치가 무조건 있어야 된다.
이터러블 이터레이터 패턴을 쓸 때 중요한 점 : done이 무한히 false가 되지 않게 하는 것.
이거 못하면 여러분들 앱이나 웹사이트 수시로 죽게된다.
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;
}
}
}
}
함수는 함수가 만들어질 때 함수의 바깥에 있는 변수들을 캡쳐하는 기능이 있다.
캡쳐해서 마치 자기들의 지역변수처럼 사용하는 기능이 있다.
이를 free variable 이라 부른다.
자기들의 인자 값도 아니고, 지역변수도 아닌데 사용할 수 있는 변수 - 자율변수
이런 자율변수가 잡혀서 함수가 해당 자율변수를 사용할 수 있는 닫혀진 공간을 클로져라고 부른다.
즉, 함수 = 클로져 이기도 하다. 자율변수를 가둬둘 수 있기 때문.
자바스크립트 특정 버전, 그리고 특정 엔진 공부 하지마!
스코프 체이닝해서 어쩌구저쩌구.. 이거 수시로 바뀌어!
그런 이야기는 3.1 엔진 이야기.
지금은 그렇게 작동 안해!
그런거 공부할 바에 그냥 컴퓨터 사이언스 원론을 이해하는 것이 더 좋다!!!!
함수라는 것은 원래 인자와 지역변수만을 쓸 수 있는데, 그게 아닌 뭔가를 쓸 수 있으면 그건 모두 자율변수고 그 자율변수가 갇히면 그게 클로저라는 것.
바깥쪽 레벨도 인식가능, Symbol.iterator라던지 N2도 인식가능 - 스코프체인
이터러블로 자료구조를 구축하는 것이 먼나라 이웃나라 이야기가 아니라는 것.
이런식으로해야 여러분들이 문법적인 혜택을 누릴 수 있다.
아 근데 이터러블, 이터레이터 인터페이스 좋긴한데 소스가 너무 길어!?
이거 좀 더 줄여서 쓸 수 없나?
GENERATOR
그것이 제너레이터 첫번째 임무다. 제너레이터는 세가지 용법이 있다.
이번시간에 배울 용법은 제일 쉬운, 이터레이터 제너레이터 용법이다.
이터레이터 제너레이터 용법
이터레이터가 너무 기니깐 그걸 좀 더 짧고 쉽게 만드는 기능을 제공해준다.
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;
}
}
}
}
위의 코드를 제너레이터 활용하면 아래처럼 짧게 만들 수 있다.
const generator = function* (max) {
let cursor = 0;
while(cursor < max) {
yield cursor * cursor;
cursor++;
}
}
function 뒤에 공간없이 * 를 붙여야 제너레이터로 인식한다.
제너레이터를 만드는 리터럴이라고 생각하면된다.
제너레이터는 제너레이터 함수를 호출할 때마다 이터레이터가 만들어진다.
그런데 제너레이터가 만드는 이터레이터는 동시에 이터러블이기도 한다.
배열을 for … of 문으로 돌리면 돌아간다.
여기서 알 수 있는 것은 배열은 이터러블이라는 것을 알 수 있다.
배열은 이터러블이면서 동시에 Symbol.iterator를 호출해보면 배열이 나온다.
배열 자체가 동시에 이터레이터이기도 한 것.
이터레이터이자 이터레이터 리절트 오브젝트
배열은 이터러블이자 이터레이터 리절트 오브젝트
제너레이터를 for … of 문으로 돌릴 수는 없다.
아까 위에 말했던 언어적 혜택은 이터러블만을 받아주기로 했다.
제너레이터가 하는 일 - 이터레이터 객체 만들기
이터러블이 하는 일 - 이터레이터 객체 만들기
그런데 for … of 뒤에 이터러블은 와도 제너레이터는 못온다.
이터러블이자 이터레이터 객체여야 for … of 로 돌릴 수 있다는 거다.
제너레이터 함수 안에서만 쓸 수 있는 yield
키워드
yield
효과 = 이터러블의 next() 반환효과
suspension
yield
에서 suspension 이라는 일이 생긴다.
suspension 이라는 일이 생기면서 이때 잠깐 이터레이터 리절트 오브젝트를 반환하게된다.
yield cursor * cursor 이 값과 done 까지 알아서 해서 객체형태로 바깥으로 내보내주고 정지한다.
바깥쪽에서 next 호출할 때까지 정지함.
이것이 얼마나 대단한 일인지 모르겠지?
우리가 여지껏 배웠던 폰노이만 구조에서 while문을 정지시킬 수 있는 방법이 있나?
없다.
그런제 제너레이터는 while문이 돌다가 yield 부분에서 멈춘다.
그리고 그 다음에 그 밑에 cursor++ 부터 다시 실행된다.
이것이 바로 suspension
이라는 기능이다.
자바스크립트는 문을 만들 때 문 하나를 레코드로 만들고 스페셜 레코드를 이용해서 제어문을 반환한다.
그럼 위의 제널레이트 함수 안의 while 문도 다 레코드로 이루어져있겠네?
따라서 자바스크립트 엔진 실행기는 원래 레코드를 돌리고 있고 진짜 노이만 머신이아니라 노이만 머신을 에뮬레이팅하고 있다는 것이다.
노이만 머신은 이들을 진짜 메모리에 적재시켜서 돌아가게하지만 자바스크립트 엔진은 얘네들을 전부 레코드로 만들었기 때문에 레코드를 돌려주고있는 가상머신을 돌리고 있다.
그런데 여기서 yield
를 하면 가상머신을 돌리다가 멈춰. 레코드를 더 이상 실행 안하고.
이것을 suspension
이라고 부른다.
언어에서 suspension
을 제공해주는 언어가 요즘은 꽤 많다.
자바는 안되지만 코틀린은 되고 C#은 원래되고 파이썬도 되고..
suspension
을 알기 전까지의 상식 - 문은 중간에 절대로 멈출 수 없어.
그런데 멈출 수 있게 되었다.
멈출 때마다 바깥에다가 이터레이터 리절트 오브젝트를 리턴한다.
cursor * cursor 를 값으로 삼고 done을 false 로 삼아 바깥으로 계속 리턴해준다는 것.
우리는 똑같은 문장을 여러번 시킬 수 있는 함수 블록을 전문적인 용어로 루틴이라고 부른다.
여러번 반복하기 때문에 루틴이라고 부른다.
그런데 제너레이터는 루틴이 아니다.
루틴은 한번 들어와서 한번에 쭉 실행되고 끝나는데, 제너레이트는 한번들어가고 여러번 나갔다가 여러번 다시 들어갈 수 있다.
이걸 코루틴이라고 부른다.
여러번 들어가고 여러번 실행시킬 수 있기 때문.
그래서 제너레이터를 학술적 용어로 코루틴이라고 부르는 것.
- 제너레이터 학술적 용어 : 코루틴
- 함수 학술적 용어 : 루틴
아까 위에서 직접짰던 loop함수, -> 아주 짧은 for … of 구문으로 한번에 해결
아주 길게 짠 iterator 객체 -> generator 함수로 아주 짧게 구현
제너레이터를 복잡하게 짰을 때 이터레이터로 번역은 할 수 있지만 도저히 번역할 엄두가 안나게됨.
이터레이터로 자려면 훨씬 복잡해지기 때문.
그런데 어떤 미친놈이 번역하긴 함. - 바벨
여러분이 제너레이터를 짜서 바벨돌려보면 가공할만한 일이 일어납니다.
done과 value는 yield가 한번에 처리한다.
yield가 값을 리턴함으로써 yield하는 동안엔 done이 다 false다.
더 이상 yield하지 않고 generator를 빠져나올 때 done이 true가 된다.
제너레이터 호출 - 이터레이터이자 이터러블 객체 반환
그래서 이터러블과 똑괕은 결과가 나오게 된다.
그래서 제너레이터는 계속 호출해도 똑같이 돈다.
이터러블이자 이터레이터 객체이기 때문에..
이터레이터 객체이기만하면 한번 돌고 끝인데, 이터러블이자 이터레이터 객체이기 때문에 계속 돌 수 있는 것이다.
배열도 마찬가지 - 이터러블이자 이터레이터 객체이기 때문에 반복을 계속해도 계속 돈단말이지. ㅇㅋ?
이터레이터 제너레이터 용법 꼭 숙지!!!
이제겨우 제너레이터 용법 한개 배운거!!!!!!!!!!!
이터레이터
상태에 대한 관리 요소가 스코프를 활용해서 자율변수를 사용하던지 아니면 인스턴스를 만들어서 필드로 next가 관리를 했어야 했는데,
제너레이터
상태를 인자, 지역변수 갱신으로 관리한다.
훨씬 간편하게, 그리고 이해하기 쉽게 관리할 수 있다.
그리고 돌리는 것도 제어문으로 돌리면되고 yield만 해주면 됨.
코드를 작성하는데 부담이 훨씬 줄어든다.