LabHub
시작하기
배우기 러닝패스 코스

컴파일러 — 작은 언어를 처음부터 끝까지 만든다

선언될 때의 환경을 들고 다닌다 — 클로저

LabHub 에서 이어서 보기

한 줄 요약

트리 워킹 인터프리터는 AST 의 노드마다 "이 노드를 만나면 무엇을 계산한다" 를 적은 함수 하나입니다. 이름은 환경(이름 → 값의 사전이 바깥 환경을 가리키는 사슬)에서 찾고, 함수 값은 선언될 때의 환경을 함께 들고 다닙니다. 이것이 클로저입니다.

왜 이게 필요했나

6모듈의 바이트코드 VM 과 8모듈의 기계어는 빠르지만, 뜻을 정의하기에는 멀리 있습니다. "&& 의 왼쪽이 거짓이면 오른쪽을 계산하지 않는다", "블록이 끝나면 안쪽 이름이 사라진다", "함수는 자기가 선언된 곳의 변수를 본다" 같은 규칙을 가장 곧게 적을 수 있는 곳이 트리 인터프리터입니다. 그래서 이 코스는 인터프리터를 기준으로 삼습니다. 뒤의 VM 과 네이티브 코드는 같은 프로그램을 돌렸을 때 인터프리터와 같은 줄을 찍어야 맞은 것입니다.

그런데 '곧게 적는다' 는 말이 '쉽다' 는 뜻은 아닙니다. 인터프리터를 파이썬으로 짜면 파이썬의 뜻이 몰래 섞여 들어옵니다.

뜻을 정의하는 쪽이 이런 것을 흘리면, 뒤의 모든 구현이 틀린 기준에 맞춰집니다.

어떻게 동작하나

환경은 사슬입니다. 블록에 들어갈 때마다, 함수를 부를 때마다 새 환경을 만들고 부모를 가리키게 합니다. 이름 찾기는 사슬을 따라 올라가고, 대입은 그 이름이 사는 환경의 칸을 바꿉니다 — 지금 환경에 새로 만들지 않습니다.

fn counter(start) {           counter(10) 을 부르면
  let n = start;                [호출 환경 {start:10, n:10}] → [전역 {counter, …}]
  fn next() { n = n + 1;        next 를 선언하는 순간, 함수 값 = (next 의 선언, 지금 환경)
              return n; }
  return next;                 counter 가 끝나도 호출 환경은 next 가 붙잡고 있어 살아남는다
}
let a = counter(10);           a() → 새 환경 {} → 부모는 붙잡아 둔 호출 환경 → n 을 11 로
print a(); print a();          11, 12 — 같은 칸을 바꾸기 때문에 이어진다

함수 값을 부를 때의 환경이 아니라 선언될 때의 환경을 부모로 삼는 것이 핵심입니다(정적 스코프). 부를 때의 환경을 부모로 삼으면(동적 스코프) a() 는 counter 의 n 을 찾지 못하고, 전역에 같은 이름이 있으면 엉뚱하게 그것을 바꿉니다. 클로저가 환경을 복사해 가도 틀립니다 — 같은 환경을 붙잡은 두 함수(값을 올리는 함수와 읽는 함수)가 서로 다른 칸을 보게 됩니다.

return 은 여러 층을 한 번에 빠져나갑니다. while 안의 if 안의 return 은 호출 자리까지 곧장 돌아가야 합니다. 파이썬으로는 예외 하나(ReturnSignal)를 던지고 호출 자리에서 잡는 것이 가장 간단합니다. 호출이 끝나면 호출 깊이를 반드시 되돌려야 하는데, 예외로 빠져나가는 길에서 빠뜨리기 쉽습니다(finally 가 필요한 자리).

실행 오류는 연산자의 위치로. a / b 에서 b 가 0 이면 / 의 줄:칸으로 "runtime error: division by zero" 를 냅니다. 의미 분석이 모르고 넘어간 타입(매개변수로 들어온 true)도 여기서 걸립니다. 동시에 살아 있는 호출이 200개를 넘으면 "stack overflow" 로 멈춥니다 — 파이썬 자신의 재귀 한도에 먼저 닿으면 사용자에게 파이썬 오류를 보여 주게 됩니다.

현장에서 만나는 모습

다음 실습에서 할 것

interp.py 에 값 규칙(64비트 감기, 0 쪽 나눗셈, bool 과 int 가르기), 환경 사슬, 식 계산(단락 평가와 실행 중 타입 검사), 문장과 블록, if·while, 함수와 클로저·return·호출 깊이, 그리고 파싱부터 실행까지 잇는 run_source 를 만듭니다. 채점기는 무작위 수 수백 쌍, 고정 프로그램, 무작위 프로그램 150편에서 찍은 줄과 실행 오류를 기준과 대조합니다.