LHJ

I'm a FE developer.

27. Abstract Loop & Lazy Execution - Generator

13 Aug 2020 » codespitz_re

Abstract Loop - Generator

const Operator = class {
    static factory(v) {
        if (v instanceof Object) {
            // Object.values - hasOwnProperty 까지 다 처리되어있다.
            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)

// 1
// 2
// 3
// 4
// 5
// 6
// 7

이전에 배웠던 코드를 Generator로 바꿨다.
아래는 이전에 작성한 코드이다.

// 팩토리
const Operator = class {
    static factory(v){
        if (v instanceof Object) {
            // Object.values - hasOwnProperty 까지 다 처리되어있다.
            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;}
    operation(f) {throw 'override';}
};

// 컴포지트
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);}
}

// operation 메서드를 통해 우리가 해야될 것들을 각 객체에 정의했다.
// 이렇게 하기 싫다면?
// operation 에 해당하는 애들을 generator에게 미루면 된다.
Operator.factory([1,2,3, {a:'abc', b:5}, 6, 7]).operation(console.log);

// 1
// 2
// 3
// a
// b
// c
// 5
// 6
// 7

const Operator = class {
    static factory(v) {
        if (v instanceof Object) {
            // Object.values - hasOwnProperty 까지 다 처리되어있다.
            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);}
    // yield 값을 반환하고 중지
    *gene(){yield this.v;}
};

const ArrayOp = class extends Operator {
    constructor(v) {super(v);}
    // yield * 다음 나오는 걸 전부 yield 처리한 후 그 다음 문으로 넘어간다.
    // v.gene() 도 이터러블 객체
    // 즉 아래 for 루프가 돌려면 각각 v마다 다 돌고 그 다음 v로 넘어간다.
    *gene(){for (const v of this.v) yield * v.gene();}
};

// 그 덕분에 for 루프 안에다 제너레이터를 넣을 수 있게 되었다. 
// 아까처럼 operation처럼 특수 구문을 만들어 실행시킨 것이 아닌 자바스크립트 표준 문법을 수용해서 작성하면 된다.
for (const v of Operator.factory([1,2,3,{a:4, b:5}, 6, 7]).gene()) console.log(v)

위 코드 해석
Generator의 yield는 iterable 객체를 반환한다.

  1. [1,2,3,{a:4, b:5}, 6, 7] 값이 Operator 객체의 static 메서드factoryv값으로 들어간다.
  2. v instanceof Object에서 true를 반환한다. : [1,2,3,{a:4, b:5}, 6, 7] 는 객체이다.
  3. !Array.isArray(v)에서 false를 반환한다. : [1,2,3,{a:4, b:5}, 6, 7] 는 배열이다. (객체이면서 배열, 배열은 객체이다.)
  4. new ArrayOp(v.map(v => Operator.factory(v))) 값을 반환한다.

  5. 즉, new ArrayOp([Operator.factory(1), Operator.factory(2), Operator.factory(3), Operator.factory({a:4, b:5}), Operator.factory(6), Operator.factory(7)]) 을 반환한다.
  6. 각각 값이 객체인지 아닌지, 객체라면 배열인지 아닌지에 따라 다음과 같이 반환한다.
    new ArrayOp([PrimaOp(1), PrimaOp(2), PrimaOp(3), ArrayOp([Operator.factory(4), Operator.factory(5)]), PrimaOp(6), PrimaOp(7)])
  7. new ArrayOp([PrimaOp(1), PrimaOp(2), PrimaOp(3), ArrayOp([PrimaOp(4), PrimaOp(5)]), PrimaOp(6), PrimaOp(7)])

  8. ArrayOp의 gene 메서드 : PrimaOp(1).gene(); -> yield 1
  9. ArrayOp의 gene 메서드 : PrimaOp(2).gene(); -> yield 2
  10. ArrayOp의 gene 메서드 : PrimaOp(2).gene(); -> yield 3

  11. ArrayOp의 gene 메서드 : PrimaOp(4).gene(); -> yield 4
  12. ArrayOp의 gene 메서드 : PrimaOp(5).gene(); -> yield 5

  13. ArrayOp의 gene 메서드 : PrimaOp(6).gene(); -> yield 6
  14. ArrayOp의 gene 메서드 : PrimaOp(7).gene(); -> yield 7

  15. for (const v of [1,2,3,4,5,6,7]) console.log(v)

함수 객체를 받지 않고 제어문을 추상화하는데 성공했다.
그 덕분에 우리는 똑같은 제어구조 안에서 매번 문을 다시 만들어야 했는데(operation), 이제는 맨 뒤의 문만 바꿔주면 된다.
위 코드로 문법적인 혜택을 다 누릴 수 있다. (spread, 해체할당, for … of 등)