CodeSpitz78 2/ 루틴 심화

CodeSpitz78 2/ 루틴 심화

목차

🌕🌑🌑

🔥 코드스피츠 수업을 수강하면서 복습한 내용을 정리했습니다.
공부 후에는 풀어서 쉬운 언어로 설명할 수 있도록 연습하자.


1. 참조 전파의 문제

  • LA는 간접적으로 B를 물고 있다.
    • 상호 참조(연쇄참조)가 되어서 서로 오염이 된다..
    • 디버깅이 어려워진다.
  • 참조값은 전체를 오염시키기 쉽다.
  • 복사본을 넘겨야한다.

2. 서브루틴의 체인

> keep의 정확한 대상
  • 인자와 지역변수를 포함하는 곳: 실행컨택스트
- 함수 콜을 return 이후로 옮겼다. - 메모리를 유지할 필요가 없으니, 리턴포인트를 다른 곳으로 지정해주면 어떨까? - 리턴포인트는 언어 수준에서 정해져있다. - 리턴포인트를 처음 호출한 함수로 옮긴다 => tail recursion - 언어수준에서 리턴포인트를 지정하기 때문에 언어마다 재귀꼬리 최적화를 지원하는 언어도 있고, 없기도 하다. - 제어문의 loop 처럼 옮긴다. - 처리하고 => 해제하고 - 제어문은 for문을 돌때마다 메모리를 유지하지 않는다. - index 변수만 남아 있다. - 제어문의 **`stack clear`** 구문. - for문은 처음 loop돌리는 블럭에 대해서 스택영역에 대해서 실행한 다음에 점프시, 앞의 stack 메모리를 전부 해제한다.

3. 재귀 꼬리 최적화 Tail Recursion

  • for문의 도움을 받지 않고도 고성능의 루프를 만들어 낼 수 있다.
  • 사파리가 지원하고 있다. 크롬 X, 엣지 X
  • tail recursion을 지원한다.
    • script time out만 난다.
    • stack over flow는 일어나지 않는다.
  • 언어마다 tail recursion이 이뤄지는 조건을 정의해 둔다.
  • 효율적인 재귀함수를 짤 수 있다.

** 예시 *

1
2
3
4
5
6
const sum = v => v + (v ? sum(v-1) : 0);
sum(3);

// 1. 3 + sum(2)
// 2. 2 + sum(1)
// 3. 1 + 0
  • 더하기 연산자는 꼬리물기 최적화를 방해한다.
  • 모든 연산자는 스택메모리를 유뱔한다.

인자로 옮겨서 스택메모리가 생기지 않도록 하였다.

연산을 인자로 옮긴다!!

1
2
3
4
5
6
7
8
9
const sum = (v, prev = 0) => {
prev += v;
return (v > 1 ? sum(v-1, prev) : prev);
}
sum(3);
// 1. sum(v:3, prev:0) return sum(2,3)
// 2. sum(v:2, prev:3) return sum(1,5)
// 3. sum(v:1, prev:5) reutrn 6
// 3번째 return값이 첫번째 sum(3)의 리턴포인트로 리턴된다.

자바스크립트에서 tail recursion 안 일으키는 연산자 (언어수준에서 정의되어있음)

  • 삼항연산자
  • &&연산자
  • ||연산자

자바스크립트는 이미 ES6에서는 tail recursive를 지원하게끔 스펙으로 지정해놓았다.
JVM은 지원하지 않는다.

  • 내 메모리는 다 해제하고 다음쪽 함수메모리의 인자메모리를 사용함으로써 해결한다.
  • 함수 외적 메모리를 알 수 있다.
    • 위의 prev는 외적 메모리라는 것을 알 수 있다.
    • for문의 지역변수도 외적 메모리라고 볼 수 있다.
    • for문의 지역변수에 갱신하는 로직과 같은개념이다.

4. 🚀🚀 재귀를 루프로 Tail Recursion to loop

  • 자유롭게 변환할 수 있어야 한다.
  • 자동으로 변환할 수 있어야 한다..
1
2
3
4
const sum = (v, prev = 0 ) => {
prev += v;
return (v > 1 ? sum(v-1, prev) : prev);
}
1
2
3
4
5
6
7
8
const sum = (v) => {
let prev = 0;
while(v > 1){
prev += v;
v--;
}
return prev;
}

5. ✨클로저✨

  • 언어적인 장치라고 생각하자.

5-1. Static state

- 이렇게 작동하는 대표적인 언어가 C언어이다. - 루틴을 만드는 것이 **문**으로 되어있다. 자바스크립트처럼 변수에 대입할 수 있는 값이 아니다. - 서브루틴을 값의 형태로 만들어내는 언어가 있고, 문으로 만들어내는 언어가 있다. - 자바의 메소드는 문이다. 클래스 내부에서만 선언되어야한다. 외부에서 대입될 수 없다. - 값으로 만들 수 있으면 실행 중간에 루틴을 만들어 낼 수 있다. - 람다 - 루틴을 람다로 볼래. - c언어의 함수포인터에서 발전해온 개념. - 런타임에 서브루틴을 만들 수 있다. **클로저는 오직 런타임 중에 루틴을 만들 수 있는 언어에서 생겨난다.** - c에서는 static 메모리 - 그렇다고 이런상황에서 모든 언어가 클로저를 생산하진 않는다.. - 언어 디자이너가 어떻게 결정했냐에 따라 다르다. - 함수를 문으로 만드는 언어의 특성

5-2. Runtime state

=> 우리가 짠 코드를 만나면 => - 실행 중간(런타임)에 루틴의 정의 자체가 태어난다. - 자기가 태어났을 때의 **자기가 갇혀있던 박스**를 바라볼 수 있는 여지가 생긴다. - 런타임에 루틴을 만들 수 있는 언어들은 루틴을 만들면 루틴정보 안에 자기가 어디서 태어났는지를 기록한다. => 자바스크립트에서는 **스코프**라고 정의한다. - **메인 루틴의 flow를 기억한다.** main flow가 흘러가는 상황을 알고 있다. - flow상에 있는 아이들을 기억하게 된다. - 노란박스에 등장하지 않는 모든 변수를 **자유변수**라고 한다. - 자유변수들은 routine과 무관하게 존재하지만 routine에서 참조할 수 있다. - routine이 한번이라도 자유변수를 갖고오게 되면 자유변수들은 마음대로 해지되거나 조작되지 못한다. **=> routine이 물고 있기 때문** - routine을 자유변수가 갇히는 공간이라고 할 수 있다. - free variables close => closure - 만약 이 상황에서 F 메소드가 외부로 유출될 경우 main flow의 메모리가 다 해지되지 못한다. - 클로저는 자유변수의 클로저이다.

6. 중첩 클로저 nested closure

  • 클로저는 루틴만이 만들어 내는 것이 아니다.
  • ES6에서는 block만 주어도 스코프가 생성된다.
  • 스코프 생성은 클로저를 만드는 행위이다.
  • block만 주어도 클로저가 생성된다.
- 연속적인 클로저가 탄생한다. - 중첩되어 있는 클로저를 마구마구 생성된다.

위 그림을 코드로 표현한다면..

1
2
3
4
5
6
7
8
9
10
11
12
13
14
window.a = 3; // main flow의 전역: global
if(a == 3) { // 첫번째 block
const b = 5;
const f1 = v => { // 첫번째 routine
const c = 7;
if (a +b > c){ // 두번째 block
return p => v + p + a + b;
// 두번째 routine
// 자유변수를 물고 있음 => 클로저
} else {
return p => v + p + a + b;
}
}
}

7. 쉐도잉

- 층층이 중첩되어있는 클로저가 있는데 각각의 클로저에서 똑같은 이름의 변수를 소유하고 있을때 일어난다.
1
2
3
4
5
6
7
8
const a = 3; // main flow에 A
if(a == 3){ // block scope
const a = 5; // ?! a가 또 있음.
const f1 = v => {
const a = 7; // ???!! a가 또있음!!
console.log(a);
}
}
  • 쉐도잉을 지원하는 언어는 가장 가까이에 있는 클로저를 사용한다.
  • 서브루틴이 밖에 있는 자유변수를 안건드리게 하기 위해서는(보호하기 위해서는) 쉐도잉 방식을 사용한다.
    • 유일한 방법은 변경하고픈 스코프의 상위에 지키고픈 변수명을 같은 변수명을 사용하여 지킨다.
    • 위의 코드는 서브루틴에서 더 상위로 못가도록 막음
1
2
3
4
5
6
7
const a = 3; // main flow에 A
if(a == 3){ // block scope
const a = 5; // 보호막을 만든 개념
const f1 = v => {
console.log(a); // 서브루틴 상위의 a에 접근
}
}

8. 코루틴

  • 커맨드 패턴
  • 실행하다가 중간에 멈출 수 있지 않을까? 라는 생각에 도달함
  • 실행하다가 중간에 멈추고 => 리턴포인트로 보내자!
  • main flow에서 sub routine에 갔다가 20번째에서 멈춤 => 다시 main flow로 흐름 => 또 다시 sub routine의 21번째에서 시작 => 다시 main flow로 흐름 => 또 다시 sub routine의 50번째에서 멈춤
  • 작성한 모든 문을 record라는 객체로 감싸서 메모리에 저장한다.
  • co routine <-> single routine
  • 자바스크립트에서는 ES6의 제너레이터에서 가능하다.

일반적인 루틴

  • 싱글루틴은 루틴이 끝까지 실행되는 것을 보장한다.

코루틴

  • 리턴말고 yield를 사용한다. (언어마다 키워드가 다르다.)

    자바스크립트에는 C# 문법이 많이 반영되어있다. (async await도..)

  • yield에서 끊어지고 리턴포인트로 돌아간다.

  • yield에서는 suspension(일시정지)가 일어난다.
  • suspension: 코루틴에 의해서 멈춰있는 상태
  • suspension을 이용해서 몇번이나 진입했다가 나갔다가를 반복할 수 있다.
  • 문인데도 불구하고 suspension를 걸 수 있다.

코루틴을 이용해서 좋은 점

  • 위의 그림의 경우 3개의 함수를 콜해야하기 때문에 R2의 3가지 버전이 필요했을 것이다.
    • 3가지 버전의 함수들이 값을 공유해야했다면 서로 받은 값들을 인자로 넘겨줘야했을 것이다.
    • 넘겨줘야할 인자가 많을 경우 점점 더 복잡한 로직이 되었을 것이다.
  • 코루틴을 이용하면 같은 메모리 내에서 지역변수가 상태를 관리한다.
    • 코드가 훨씬 더 쉬워진다.

루프에서 코루틴

  • 루프 내부에 yield가 있을 경우 루프가 조건이 만족할때까지 반복하는 것이 아니라 yield에서 멈추고 반환한다.
  • 두번째로 다시 함수를 호출하면 이전에 멈췄던 반복문에서 다시 반복문이 시작하게 된다.
  • 루프가 돌때마다 진행하게 된다.
  • 드디어 루프를 멈출 수 있게 할 방법이 생김.

제너레이터

1
2
3
4
5
6
7
8
9
const generator = function*(a){
a++;
yield a;
a++;
yield a;
a++;
yield a;
}
const coroutine = generator(3);
  • 제너레이터 자체는 코루틴이 아니다.
  • 제너레이터 함수는 제너레이팅 하는 함수이기 때문에 코루틴이 아니고, 제너레이터를 호출하는 함수의 결과값이!! 코루틴이다.
  • 3은 상수인 것 처럼 코루틴이 만들어진다. 코루틴은 인자가 3인 것을 기억하고 태어난다.
1
2
3
let result = 0;
result += coroutine().value;
console.log(result);
  • 코루틴은 iterator result object를 반환한다.
    • 이 오브젝트에는 done키와 result키값이 존재한다.
    • value에 값이 존재한다.
      • yield로 출력된 값
1
2
3
let result = 0;
result += coroutine.next().value;
console.log(result); // 4
  • next()라는 메서드를 통해 전진을 시켜야한다.

과제

다음의 코드는 구구단을 출력한다.
이를 만족하는 제네레이터를 작성하시오.

1
2
3
4
5
6
7
8
9
10
11
12
const generator_Joy = function*(i, j){
for(let a = 1; a < i+1; a++){
for(let b = 1; b < j+1; b++){
const c = a * b
yield [ a, b, c ]
}
}
};

for(const [i, j, k] of generator_Joy(9,9)){
console.log(`${i} x ${j} = ${k}`);
}
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
28
// 어떤 분의 의식의 흐름
const generator_Aus1 = function*(i, j){
for(let a = 1, b = 1; a < i ; b++){
if(b === j){
a++;
b=0;
}
yield [ a, b, a * b ]
}
};

const generator_Aus2 = function*(i, j){
for(let a = 1, b = 1; a <= i ; b++){
if(b === j){
a++;
b=0;
}
yield [ a, b, a * b ]
}
};

const generator_Aus3 = function*(i, j){
for(let a = 1, b = 1;
a <= i ;
b >= j ? b = 0 + !!(a++) : b++){
yield [ a, b, a * b ]
}
};
📚