코드스피츠77 ES6_4회차_Abstract loop & lazy execution

코드스피츠77 ES6_4회차_Abstract loop & lazy execution

목차

🌕🌑🌑

TL;DR

복습할때나 이 문제가 까먹었을 즈음에 풀어보기

  1. iterable객체 완성하기
    ??? 구해보기
    (2가지 방법으로)
    1
    2
    3
    4
    5
    6
    7
    {
    [Symbol.iterator](){return this;}
    data: [{a:[1,2,3,4], b:'-'}, [5,6,7], 8, 9],
    next(){
    return ???
    }
    }

코드스피츠 강의 정리록입니다.


4강 키워드: Generator의 지연실행 측면과 이를 위한 추상루프화


1. abstract loop: 루프의 추상화

1.1 complex recursion 복잡한 반복

단순한 배열 루프인 경우 간단히 이터레이션을 작성할 수 있다.

1
2
3
4
5
6
7
8
9
10
{
[Symbol.iterator](){return this;},
data:[1,2,3,4],
next(){
return {
done:this.data.length == 0,
value:this.data.shift()
};
}
}

복잡한 다층형 그래프는 어떻게 이터레이션 할 것인가?

es6와 es6이전의 객체 리터럴의 큰 차이점이 무엇인가

  1. es6이전
    • 기존의 객체 리터럴에서는 객체 리터럴의 순서가 없다.
    • 자바로 따지면 hash memory로 되어있다.
  2. es6
    • es6의 객체 리터럴은에서는 객체 리터럴의 순서가 있다.
    • linked hash memory로 되어있다.
    • 반드시 순서대로 해석된다.

문제

1
2
3
4
5
6
7
{
[Symbol.iterator](){return this;}
data: [{a:[1,2,3,4], b:'-'}, [5,6,7], 8, 9],
next(){
return ???
}
}
생각해보기 (글쓴이 생각..)
  1. iterable 프로토콜 유지하기
  2. 배열 요소들의 타입 검사
    1. 객체일 경우
      객체 내부 키값의 요소들의 타입 검사
      1. 객체일 경우
      2. 배열일 경우
      3. 일반 값인 경우
        => 처음 타입 검사와 같은 흐름이므로 해당 flow를 recursive 되도록 한다.
        • 어떻게 같은 루프를 다시 태우지..
    2. 배열일 경우
      배열 요소의 타입 검사
      1. 객체일 경우
      2. 배열일 경우
      3. 일반 값인 경우
        => 처음 타입 검사와 같은 흐름이므로 해당 flow를 recursive 되도록 한다.
        • 어떻게 같은 루프를 다시 태우지..
    3. 일반 값인 경우
      값 반환

cf__1. 루프를 어떻게하면 잘 짤 수 있을까.

루프를 잘 짜고
보다 어려운 로직을 짜는 방법은
내 머리가 선언한 변수만큼을 추적할 수 있어야 한다…

기본적인 알고리즘 전략은 최대한 상태를 덜쓰는것. 내 머리가 추적할 정도로..


정답

각각이 컨테이너 형이라면
그 컨테이너를 다시 해체해서 다시 배열에 붙여준다.
더 이상 컨테이너 형이 아닐 때까지

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 프로토콜만 맞춰주면 된다.
// {
// done: boolean,
// value: 출력값..
// }
{
[Symbol.iterator](){return this;}
data: [{a:[1,2,3,4], b:'-'}, [5,6,7], 8, 9],
next(){
let v;
while(v = this.data.shift()) {
// shift가 더이상 안되면 undefined
switch(true){
case Array.isArray(v):
// 2번째 케이스문의 객체 타입가드 조건문이 array 타입도 포함되므로, array만을 위한 조거문을 먼저 작성한다.
this.data.unshift(...v);
break;
case v && typeof v === 'object':
for(var k in v) this.data.unshift(...x);
break;
default:
return {value:v, done: false}
}
return {done: true}
}
}
}
정답 내의 오류

for(var k in v)

  1. for…in문은 prototype의 key까지 다 나오기 때문에 hasOwnProperty를 조건에 추가해야한다.

    • hasOwnProperty는 자신의 고유 속성, 즉 상속받은 프로퍼티가 아닌 순수 자신의 속성인 경우에만 true라는 값을 반환하는 특징이 있습니다.
    1
    2
    3
    for(var k in v){
    if(v.hasOwnProperty(k)) this.data.unshift(...x);
    }
  2. 현재 상태에서는 순서 보장이 안된다.

    1
    2
    3
    let x = [];
    for(var k in v) x.push(v[k])
    this.data.unshift(...x);

한번더 리팩토링

값인 타입을 v instanceof Object로 판단할 수 있다!

1
2
3
4
5
6
7
8
9
10
11
12
13
{
[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}
}
}

정리

자바스크립트 언어는 2가지로 되어있다.

  1. 언어자체의 문법적인 내용,
  2. 클래스 라이브러리
    자바스크립트의 클래스 라이브러리는 코어객체 (Built-in)
    Math, Date, RegEx…
    => 표준으로 제공되고 있는 언어 표준의 일부
    => 언어 스팩의 일부

코어객체에 있는 메서드들을 사용하는 것이
기본 언어자체의 문법을 사용하는 것보다 더 안전하다.

ex. for..in보다 Object.values

재사용성 올리기
unshiftshift로 인해서 data의 배열이 빈배열이 되는 상황이 된다.
클래스로 묶어서 매번 부를때마다 인스턴스를 반환하는 형태로 만들자.

cf__2. 함수를 변수에 할당하자.

함수를 정의할때 3가지 스타일

  1. 함수 표현식
  2. 함수 선언문
  3. Function…

함수는 값이기 때문에 변수에 할당하는 형식이 맞다. 더 정확하게 호이스팅에 의존하지 않고 어느 시점에 함수를 만들었다를 명확하게 코드로 인지할 수 있기 때문에 function으로 시작하는 함수 정의방법은 아예 금지시키고 못쓰게 하는 경우가 많다. lint에도 함수선언문 금지룰이 있다.

클래스도 하나의 값이다. 변수에 할당이 된다. 변수의 할당 없이 class..로 시작하는 선언방법은 이 클래스가 언제 만들어졌는지 모호하게 만드는 관점이 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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]);

shift를 사용하는 것은 data가 배열이 와야하는 조건이 있기 때문에
data의 사본을 만들때 아예 배열화시킨다.
const data = [JSON.parse(JSON.stringify(this.data));]

if절을 보면 mandatory가 아닌 optional.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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]);
console.log([...a]);


1.2 abstract loop

위의 루프는 목적이 있는 루프이다.
목적이 있는 루프를 만들고 목적을 바꾸면 루프를 다시 짜야한다.

다양한 구조의 루프와 무관하게 해당 값이나 상황의 개입만 하고 싶은 경우

1
2
3
4
5
6
7
8
9
10
11
12
13
(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);
}
}
}

이 상황에서 f(v)를 호출하는 body에 본래 목적과 다른 로직이 추가 될 경우,
같은 로직을 복사하고 원하는 로직을 추가해야하는 상황이 발생한다.
문으로 로직을 작성할 때는 별다른 방법이 없다.
=> 제어문을 재활용할 수 없으므로 중복정의할 수 밖에 없다.
=> 문은 사용하고 나면 재활용할 수 없는데 이걸 어떻게 객체화하지?

결국 제어문을 직접 사용할 수 없고 구조객체를 이용해 루프 실행기를 별도로 구현

구조를 추상화해보자.

  • 루프 공통 골격
  • 개별구조객체
cf__3. if문을 제거하는 방법

우리가 배우는 거의 모든 priority 기법은 if를 어떻게하면 제거할까에 대한 연구일 수도 있다. 사람은 if가 많아지면 감당이 안된다. 조건이 많아져서..
if를 어떻게하면 제거할 수 있을까?

  • 정단한 이유라면 바로 제거할 수 없다. 필요에 의해서 태어났기 때문에
  • if로 나눠지는 경우의 수만큼의 값을 미리 만들어 놓고, 바깥쪽에서 그 값을 선택해서 들어오게 하는 수밖에 없다.

아래는 if가 3개이다.

  1. array일때
  2. object일때
  3. primitive일때
    3개만큼의 객체를 만들어놓고 바깥쪽에서 객체를 결정하게 끔 만들어 주면 값으로 분리할 수 있게 된다. => 이렇게 if문이 하나씩 제거된다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    (data, f) => {
    let v;
    while(v = data.shift()){
    if(!(v instanceof Object)){
    f(v)
    } else {
    if(!Array.isArray(v)) v = Object.values(v);
    data.unshift(...v);
    }
    }
    }

팩토리 + 컴포지트

  • 선택기는 팩토리 패턴으로
  • 각각의 조건문을 컴포지트 패턴으로
cf__4. 팩토리 패턴 참고
  • 비슷한 객체를 공장에서 찍어내듯이 반복적으로 생성할 수 있게 하는 패턴
  • 컴파일 시점에 구체적인 타입(클래스)을 몰라도 객체 생성이 가능하다
  • 팩토리 패턴의 가장 흔한 사례는 Object() 를 이용한 객체 생성시, 주어지는 값의 타입에 따라 String, Boolean, Number 등으로 객체가 생성되는 것이다.
cf__5. composite 패턴
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 😵😵😵
const Operator = class {
static factory(v){
if(v instanceof Object){
if(!Array.isArray(v)) v = Object.values(v);
// --- object도 배열로 환원시켰기 때문에 현재 v는 배열임.
return new ArrayOp(v.map(v => Operator.factory(v)))
} else return new PrimaOp(v);
}
constructor(v){this.v = v}
operation(f){throw 'override'}
}

// primitive 타입 처리하는 클래스
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)
  • 케이스를 더 많이 늘리는 것도 가능하다..

2. lazy execution

yield

yield*

📚