Generator
-
정의
- function * : Generator 리터럴
- Generator를 호출하면 Iterator 객체가 나온다(Iterator이면서 Iteratable한 객체를 반환)
- for of 뒤에는 Iteratable한 객체가 와야하기 때문에 generator는 올 수 없다.
- yield를 사용하면 next()룰 호출한것 처럼 value를 리턴한다(이러한 것을 서스펜스)
- generator 함수에서 yield를 통해 나오면 done : false, generator 함수가 끝나면 done : true
- 서스펜션이 일어나면서 Iterator result object를 반환 === next()
- 서스펜션 : Statement은 중간에 멈출수 없는데 Statement을 멈추는 것을 서스펜션
- Coroutine(코루틴)의 형태
- 실행 후 중간에 멈춰서 나가고 다시 들어와서 반복적으로 실행
- 여러번 진입하고(진입 위치가 달라진다 여러번 반환한다) 들어올때 중간부터 들어올수 잇다
- Roution : 처음부터 들어가서 문 전체를 실행시키는 것 == 함수
- 이터레이너터에서는 제어문(for / while) 까지 중단 => 제네레이터 서스펜션
-
Iteratable과 차이(객체 상태에 대한 관리요소)
- Iteratable
- 스코프를 이용하여 자유변수를 사용하던지 인스턴스로 만들어서 필드로 관리
- Generator
- 지역변수와 매개변수로 관리한다.
function*(max){ let cursor = 0; while(cursor < max){ yield cursor * cursor;cursor++; } };
- Iteratable
추상화 루프
-
정의
- 제네레이터의 지연실행 측면과 추상 루프화에 대해 설명
- 루프문을 작성하는 것보다 이터러블 객체로 추상화하면 객체의 상태값을 이터레이터 객체가 가지고 있어서 루프를 반복실행할수 있다.
- 루프의 역할이 줄어들며, 기준 루프는 제어문의 역학을 가지고 있는데, 이터러블은 자체에서 선택하므로 더 많은 결정권을 가진다.
- 객체안에 루프안에 상태를 보관할테니까 알아서 실행만 하면 알아서 루프처럼 실행
- 루프 개념에 대한 개념을 추상화하면 객체가 된다
-
루프
- 이터레이션 : 똑같은 행위를 반복
- 리컬시브 : 똑같은 행위를 반복할수 없고 반복하면서 행위를 평가
Complex recursive
-
단순한 배열을 루프인 경우는 간단히 이터레이션을 작성할 수 있다.
- 모던브라우저에서는 무조건 이터러블 객체만을 받는다(리턴은 this 안에 이터레이터 객체가 있다.) 반드시 구현해야 한다
const iter = { [Symbol.iterator]() { return this }, arr: [1, 2, 3, 4], next() { return { done: this.arr.length === 0, value: this.arr.pop(), } }, }
-
만약 복잡한 다층형 형태의 데이터 구조는 어떻게 할 것인가?
{ [Symbol.iterator](){return this;}, data: [ {a : [1,2,3,4] , b : '-'}, [5,6,7], 8, 9]], next (){ let v; while(v = this.data.shift()){ // 데이터에 대한 평가만 이루어지는 것은 좋지 않다. // 무한루프 방지 제한 조건을 추가(&& v count < 100) switch(true){ case Array.isArray(v): this.data.unshift(...v); break; case v && typeof v == 'object': for(var k in v) this.data.unshift(v[k]); break; default: return {value:v, done:false}; } } return {done:true}; } } // es6 버전 { [Symbol.iterator](){return this;}, data:[{a:[1,2,3,4], b:'-'}, [5,6,7], 8, 9], next(){ let v; while(v = this.data.shift()){ if(!(v instanceof Object)) return {value:v}; if(!Array.isArray(v)) v = Object.values(v); this.data.unshift(...v); } return {done:true}; } }
-
Class로 만들어서 처리(유지보수 하기 어려운 코드)
const Compx = class { constructor(data) { this.data = data } [Symbol.iterator]() { const data = JSON.parse(JSON.stringify(this.data)) return { next() { let v while ((v = data.shift())) { if (!(v instanceof Object)) return { value: v } if (!Array.isArray(v)) v = Object.values(v) data.unshift(...v) } return { done: true } }, } } } const a = new Compx([{ a: [1, 2, 3, 4], b: '-' }, [5, 6, 7], 8, 9]) console.log([...a]) console.log([...a])
-
제네레이터를 활용한 코드
- 이터레이터가 없으며, 상태값을 반환하는 것도 없다(제네레이터가 이터레이터를 만들어 주며, 상태값을 반환한다)
- 단, 제네네이터를 호출해야 한다
const Compx = class { constructor(data) { this.data = data } *gene() { const data = JSON.parse(JSON.stringify(this.data)) let v while ((v = data.shift())) { if (!(v instanceof Object)) yield v else { if (!Array.isArray(v)) v = Object.values(v) data.unshift(...v) } } } } const a = new Compx([{ a: [1, 2, 3, 4], b: '-' }, [5, 6, 7], 8, 9]) console.log([...a.gene()]) console.log([...a.gene()])
Abstract Loop
-
정의
- 다양한 구조의 루프와 무관하게 해당 값이나 상황의 개입만 하고 싶은 경우
- Complex recursive의 Generator는 목적이 바뀌면 루프가 변경되어야 한다.
- 제어문을 재활용할 수 없으므로 중복정의할 수 밖에 없다.(중복정의 보다는 구조객체를 만들어서 재활용 할 수 없을까??)
- 결국 제어문을 직접 사용할 수 없고 구조객체를 이용해 루프실행기를 별도로 구현
;(data, f) => { let v while ((v = data.shift())) { if (!(v instanceof Object)) { // v에 대해서 어떤한 작업을 추가하려면 ???? f(v) } else { if (!Array.isArray(v)) v = Object.values(v) data.unshift(...v) } } } // v의 작업을 추가하려면 해결책은 중복정의 밖에 없다. ;(data, f) => { let v /// --- 루프 공통 골결 --- while ((v = data.shift())) { if (!(v instanceof Object)) { // 개별 구조 객체 1 console.log(v) f(v) } else { // 개별 구조 객체 2 if (!Array.isArray(v)) v = Object.values(v) data.unshift(...v) } } /// --- 루프 공통 골결 --- }
-
루프 추상화 실행
-
팩토리(선택기) + 컴포지트(각 선택기에 해당하는 객체) 패턴으로 처리
// 오브젝트 처리 const Operator = class { static factory(v) { if (v instanceof Object) { if (!Array.isArray(v)) v = Object.values(v) return new ArrayOp(v.map((v) => Operator.factory(v))) } else return typeof v === 'string' ? new StringOp(v) : new PrimaOp(v) } constructor(v) { this.v = v } operation(f) { throw 'override' } } // 문자열 처리 const StringOp = class extends Operator { constructor(v) { super(v) } operation(f) { for (let i = 0; i < this.v.length; i++) { f(this.v[i]) } } } // 기본값 처리 const PrimaOp = class extends Operator { constructor(v) { super(v) } operation(f) { f(this.v) } } // 배열 처리 const ArrayOp = class extends Operator { constructor(v) { super(v) } operation(f) { for (const v of this.v) v.operation(f) } } Operator.factory([1, 2, 3, { a: 4, b: 5 }, 6, 7]).operation(console.log)
-
팩토리(선택기) + 컴포지트(각 선택기에 해당하는 객체) + ES6 Iterable
const Operator = class { static factory(v) { if (v instanceof Object) { if (!Array.isArray(v)) v = Object.values(v) return new ArrayOp(v.map((v) => Operator.factory(v))) } else return new PrimaOp(v) } constructor(v) { this.v = v } *gene() { throw 'override' } } const PrimaOp = class extends Operator { constructor(v) { super(v) } *gene() { yield this.v } } const ArrayOp = class extends Operator { constructor(v) { super(v) } *gene() { for (const v of this.v) yield* v.gene() } } for (const v of Operator.factory([ 1, 2, 3, { a: 4, b: 5 }, 6, 7, ]).gene()) console.log(v)
-
위에 메소드는 복잡해서 별도로 선택하는 팩토리가 필요하지만, 일반적으로 선택할 수 있게 만들면 라우터가 된다.
- 웹서버는 url 경우의 수만큼 처리해주는 것, 경우의 수를 설정해주면 등록된 애들중에서 찾아 그 등록된 애들을 보여준다.
- 동적으로 평가하니까 일반화가 어렵다. 이런 경우는 애들을 선택적으로 팩토리가 만들어준다.
-
yeild 위임
- yield : 내거를 반환하고 중지
- yield _ 제네레이터 함수 : _ 다음에 있는 제네레이터의 yield를 다 실행시키고 다음으로 넘어감(yield 위임)
-
Lazy Execution
-
정의 : 최소한의 호출하며, 필요없는 계산을 하지 않는것
const odd = function*(data){ for(const v of data){ console.log("odd", odd.cnt++); if(v % 2) yield v; } }; odd.cnt = 0; for(const v of odd([1,2,3,4])) console.log(v); // 결과 0 'odd' 0 1 'odd' 1 'odd' 2 3 'odd' 3 const take = function*(data, n){ for(const v of data){ console.log("take", take.cnt++); if(n--) yield v; else break; } }; take.cnt = 0; for(const v of take([1,2,3,4], 2)) console.log(v); // 결과 0 'odd' 0 1 'odd' 1 2 'odd' 2 for(const v of take(odd([1,2,3,4]), 2)) console.log(v); // 결과 0 0 'odd' 0 'take' 0 1 'odd' 1 'odd' 2 'take' 1 3 'odd' 3 const Stream = class{ static get(v){return new Stream(v);} // 팩토리 constructor(v){ this.v = v; this.filters = []; } add(gene, ...arg){ // 커링 this.filters.push(v=>gene(v, ...arg)); return this; } *gene(){ let v = this.v; for(const f of this.filters) v = f(v); yield* v; } }; const odd = function*(data){ for(const v of data) if(v % 2) yield v; }; const take = function*(data, n){ for(const v of data) if(n--) yield v; else break; }; // 스트림 구현 for(const v of Stream.get([1,2,3,4]).add(odd).add(take, 2).gene()) console.log(v); // 결과 1 2
-
정리
- 스트림을 통해 제네레이터를 이어줄수 있다. 지연실행을 제어문으로 구현할 수 있다.
- 함수형에서는 함수를 호출할때까지 지연시킬수 있었는데, 제네레이터는 yield를 통해서 지연시킬 수 있기 때문이다.
동기로직 플로우 컨트롤
- 프로그램이 한꺼번에 적재되고 실행이 되면서 플로우가 일자로 생기는데, 제어문을 통해 플로우 컨트롤을 해줌(동기 flow)
- 동기흐름에서 원래는 한번 실행을 시키면 실행환경에서 못 빠져나오는데, 코루틴을 활용해서 다른곳을 다시 갔다가 다시 오는게 가능하다.
참고
- 알고리즘 : 상태와 제어문을 통해 원하는 값을 얻는것
- 지연실행은 함수의 특권이다.
- 함수가 실행되기 전까지는 제어문을 실행 시키지 않을 수 있다.(함수 호출 지연실행)
- 제네레이터도 yield를 통해 밑에 있는 제어문을 실행시키지 않을 수 있다.(코루틴)
- 자바스크립트에서 함수를 정의하는 방법
- 함수선언문 : 일반적인 함수의 형태 호이스팅 발생
- 함수표현식 : 함수를 변수에 할당해서 사용
- 함수 선언보다 함수 또한 값이기 때문에 호이스팅에 의자하지 않고 값에 할당하여 사용(클래스(== 함수) 또한 하나의 값)
- 유지보수가 좋은 코드
- 코드는 읽을 수 있는것, 주석은 오해를 불러일으키기 쉽다.
- JSON.stringify, JSON.parse는 그 어떤것 보다 빠르다(이유 : C로 처리함)
- IF를 제거하는 방법
- IF로 나누어진 경우의 수 만큼의 값을 미리 만들어 놓고 바깥쪽에서 선택해서 들어오게 하면 IF 하나를 제거할 수 있다.
- 이렇게 곁곁이 쌓으면 조건을 하나씩 제거 할 수 있다.
- 현대 언어들은 일반적으로 코드를 컴파일하고 돌리면 오퍼랜드(오피코드)로 바뀌어 컴퓨터를 넣어주면 flow control 불가
- 오피코드를 객체로 감싸 다시 오피코드로 만들어야지 실행 중간에 명령을 선택할 수 잇게 한다 => 코루틴
- 최적화를 신경쓰지 말아라. 브라우저가 다 해준다.
- 굳이 하고 싶다면, 주어진 내장객체(Object.assign, for-each...)의 함수를 사용하면 최적화 처리를 했기때문에 사용하는게 제일 좋다.
- %(모듈러)는 음의 정수에서는 작동하지 않기 때문에 Math.absolute를 활용하여 만들어줘야 한다.