🔥 코드스피츠 수업을 수강하면서 복습한 내용을 정리했습니다. 공부 후에는 풀어서 쉬운 언어로 설명할 수 있도록 연습하자.
1. 참조 전파의 문제
LA는 간접적으로 B를 물고 있다.
상호 참조(연쇄참조)가 되어서 서로 오염이 된다..
디버깅이 어려워진다.
참조값은 전체를 오염시키기 쉽다.
복사본을 넘겨야한다.
2. 서브루틴의 체인
> keep의 정확한 대상
인자와 지역변수를 포함하는 곳: 실행컨택스트
- 함수 콜을 return 이후로 옮겼다.
- 메모리를 유지할 필요가 없으니, 리턴포인트를 다른 곳으로 지정해주면 어떨까?
- 리턴포인트는 언어 수준에서 정해져있다.
- 리턴포인트를 처음 호출한 함수로 옮긴다 => tail recursion
- 언어수준에서 리턴포인트를 지정하기 때문에 언어마다 재귀꼬리 최적화를 지원하는 언어도 있고, 없기도 하다.
- 제어문의 loop 처럼 옮긴다.
- 처리하고 => 해제하고
- 제어문은 for문을 돌때마다 메모리를 유지하지 않는다.
- index 변수만 남아 있다.
- 제어문의 **`stack clear`** 구문.
- for문은 처음 loop돌리는 블럭에 대해서 스택영역에 대해서 실행한 다음에 점프시, 앞의 stack 메모리를 전부 해제한다.
- 이렇게 작동하는 대표적인 언어가 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; constf1 = v => { // 첫번째 routine const c = 7; if (a +b > c){ // 두번째 block returnp => v + p + a + b; // 두번째 routine // 자유변수를 물고 있음 => 클로저 } else { returnp => 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가 또 있음. constf1 = 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; // 보호막을 만든 개념 constf1 = 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에서 멈추고 반환한다.