00 — Overview (개관)
한 페이지로 보는 전체 흐름
MicroHs는 2023-05-25에 두 개의 파일로 시작했다: 람다 대수를 컴비네이터로
컴파일하는 하스켈 227줄짜리 Lam.hs와, 그 결과로 나온 컴비네이터 그래프를
평가하는 C 539줄짜리 eval.c다. 첫 커밋 ccb31388("First version,"
첫 버전)은 한 자리에서 다 읽을 수 있을 만큼 작다. 이름값 하는 파서도, 오류
처리도, 모듈도 없다 — 그저 람다 표현식을 SKI 스타일 컴비네이터로 컴파일해
실행할 만큼의 장치만 있을 뿐이다. 바로 이 지점에서 시작하는 이유가 여기
있다: 시스템 전체를 머릿속에 담을 수 있다는 것.
그 뒤로 작성자 레나르트 아우구스트손(Lennart Augustsson)은 이 두 파일을
진짜 컴파일러로 키워 나간다. 나흘째, 커밋 a9178695는 노골적으로 "Make a
real compiler."(진짜 컴파일러 만들기)라는 제목을 달고 있다 — 장난감
수준의 평가기가 실제 파서, 제대로 된 노드 표현, 최적화기를 갖추게 된다.
여기서부터의 성장은 선형적이지도, 깔끔하지도 않다. 다음 날 커밋
17587b2d는 "Hack around a problem."(문제를 우회하다)이라 불린다 —
동작은 하는 수정이며, 설계 결정으로 포장하지 않고 있는 그대로를 설명한다.
이 솔직함은 역사 전체를 관통한다: 2023-07-03자 6f08f029는 "Running IO
still doesn't work. :("(IO 실행은 아직도 안 됨. :()라고 적혀 있으면서도
그대로 커밋된다. 망가진 상태를 커밋하는 것이 바로 이 작업 방식이기
때문이다.
이후 일주일 남짓 동안 언어가 채워진다: let, 중위 연산자, 튜플, 리스트,
문자열·문자 리터럴, do 표기법, 모듈, 프렐루드, 리스트 컴프리헨션 —
표층 문법은 하스켈이라고 알아볼 수 있는 무언가로 수렴해 가지만, 백엔드는
여전히 작은 C 런타임 위의 컴비네이터 그래프 축약이다. 이와 병행해
컴파일러는 바깥 세상과 대화하는 법을 익히고(모나딕 IO, 프로그램 상태를
파일로 직렬화하는 것) 나름의 실험 노트 방식 작업 습관도 갖춰 나간다:
명령줄 플래그, 임시방편 테스트, 첫 README.
이야기의 후반부는 컴파일러가 GHC를 벗어나 자라는 과정이다. 7월부터
컴파일러 자신의 소스를 스스로 컴파일하게 만드는 작업을 둘러싸고 커밋이
쌓인다 — 모듈이 하나씩 이식될 때마다 "Make it compile with uhs"(uhs로
컴파일되게 하기), "Make Desugar compile with uhs"(Desugar가 uhs로
컴파일되게 하기) 같은 커밋 메시지가 반복해서 나타난다. 2023-07-20,
298c9824가 uhs를 mhs로 이름을 바꾸고, 같은 기간 안에 컴파일러가
자기 자신을 성공적으로 컴파일한다. 8월 초까지 이어지는 마지막 구간은
힌들리-밀너 타입 검사기를 추가하며, 셀프 호스팅된 컴파일러가 타입
검사까지 갖춘 채 부트스트랩하게 만든다 — a38c64ce, "Now bootstraps
with type checking."(이제 타입 검사까지 갖춰 부트스트랩한다),
2023-08-10에서 이 기록은 멈춘다.
77일 동안 한 명의 작성자가 남긴 232개의 커밋이며, 이 저장소의 git 역사가 1차 출처다 — 여기 담긴 그 무엇도 기억에 의존한 재구성이 아니다. 이 책의 나머지 부분에 대한 독자와의 약속은 이렇다: 1장부터 9장까지 순서대로 읽고(각 장은 바로 앞 장이 남긴 상태를 전제로 한다), "직접 해보기" 블록은 그냥 읽고 넘어가지 말고 실제로 버릴 워크트리에서 실행해 본다. 이 자료의 가치는 무엇이 바뀌었는지의 요약이 아니라, 동작하는 코드가 커밋 단위로 진화해 가는 모습을 지켜보는 데 있다.
타임라인
| Ch | Dates (2023) | Commit range | Chapter | Summary |
|---|---|---|---|---|
| 01 | 05-25 | ccb31388 + 21168890 | 씨앗 | 씨앗: Lam.hs + eval.c |
| 02 | 05-26 – 05-30 | 21168890..b56ba28b | 진짜 컴파일러 | 진짜 컴파일러: 파서, 노드 표현, 최적화기 |
| 03 | 05-30 – 06-01 | b56ba28b..95ff6a34 | 언어 키우기 | 언어 키우기: let, 중위 연산자, 튜플, 리스트, 문자열, do |
| 04 | 06-01 – 06-02 | 95ff6a34..8c5fbb1a | 세상과 대화하기 | 세상과 대화하기: 모나딕 IO, 직렬화 |
| 05 | 06-02 | 8c5fbb1a..8018eec8 | 실험 노트: 작업 습관 | 실험 노트 방식 작업 흐름: 플래그, 테스트, 첫 README |
| 06 | 06-02 – 06-07 | 8018eec8..41d6a7e8 | 하스켈이 되어 가기 | 하스켈이 되어 가기: 모듈, 프렐루드, 컴프리헨션 |
| 07 | 07-01 – 07-18 | 41d6a7e8..a824a175 | 셀프 호스팅 총력전 | 셀프 호스팅 총력전: "compile with uhs" |
| 08 | 07-18 – 07-20 | a824a175..298c9824 | 부트스트랩 | 부트스트랩: 컴파일러가 스스로를 컴파일하다 |
| 09 | 07-20 – 08-10 | 298c9824..a38c64ce | 마침내 타입 | 마침내 타입: HM 검사기, 타입이 붙은 부트스트랩 |
6장과 7장 사이의 도약에 주목하자: 6장의 마지막 커밋 41d6a7e8는
2023-06-07 날짜이고, 7장의 첫 커밋 44004020은 2023-07-01 날짜다. 이
저장소의 역사 어디에도 24일의 공백을 설명해 주는 내용은 없다 — 로그는
그저 다시 이어질 뿐이다. 이 책에서 장의 경계는 고정된 주기가 아니라
작업의 주제적 흐름을 따른다.
이 브랜치 사용법
각 장은 어느 커밋 범위를 살펴봐야 하는지, 거기서 무엇을 빌드하고 실행해야 하는지 알려준다. 이 책 전체에서 쓰는 관례는, 메인 클론을 건드리는 대신 커밋을 각자의 워크트리로 체크아웃하는 것이다:
git worktree add /tmp/mhs-<shortsha> <shortsha> && cd /tmp/mhs-<shortsha> && nix develop $MICROHS -c make <target>
$MICROHS는 최상위 README.md의 안내대로 한 번 설정해 둔, 여러분의
클론의 절대 경로다. 커밋 작업이 끝나면 해당 워크트리를 제거한다:
git worktree remove --force /tmp/mhs-<shortsha>
이 역사를 누군가의 요약을 믿는 대신 직접 읽는 것이 이 책의 방법론 전부다. 실제 예시로, 첫 커밋을 그대로 보인다:
$ git show --stat ccb31388
.gitignore | 6 +
Lam.hs | 227 ++++++++++++++++++++++++++
Makefile | 14 ++
eval.c | 539 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 786 insertions(+)
네 개의 파일, 786줄, 커밋 하나. 이것이 1장이 출발하는 시스템의 전부다.
이후 어느 장에서 어떤 커밋이 무엇을 했다고 주장하면, 직접 git show <sha>를 실행해 확인할 수 있다.
이 책이 다루지 않는 것
이 책은 하스켈 튜토리얼이 아니다 — 하스켈을 이미 편하게 읽을 수 있다고
전제한다. 현재의 MicroHs를 위한 레퍼런스도 아니다; 이 프로젝트는 이
기록이 끝난 뒤로도 오래 계속되었고, 그 이후의 역사는 이 책의 범위 밖이다.
이 책은 git 역사의 특정하고 한정된 한 구간 — ccb31388(2023-05-25)부터
a38c64ce("Now bootstraps with type checking.", 2023-08-10)까지 — 을
자세히 읽어 나가는 것이다. 의도적으로 거기서 멈춘다.