🌐 Read in English

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장까지 순서대로 읽고(각 장은 바로 앞 장이 남긴 상태를 전제로 한다), "직접 해보기" 블록은 그냥 읽고 넘어가지 말고 실제로 버릴 워크트리에서 실행해 본다. 이 자료의 가치는 무엇이 바뀌었는지의 요약이 아니라, 동작하는 코드가 커밋 단위로 진화해 가는 모습을 지켜보는 데 있다.

타임라인

ChDates (2023)Commit rangeChapterSummary
0105-25ccb31388 + 21168890씨앗씨앗: Lam.hs + eval.c
0205-26 – 05-3021168890..b56ba28b진짜 컴파일러진짜 컴파일러: 파서, 노드 표현, 최적화기
0305-30 – 06-01b56ba28b..95ff6a34언어 키우기언어 키우기: let, 중위 연산자, 튜플, 리스트, 문자열, do
0406-01 – 06-0295ff6a34..8c5fbb1a세상과 대화하기세상과 대화하기: 모나딕 IO, 직렬화
0506-028c5fbb1a..8018eec8실험 노트: 작업 습관실험 노트 방식 작업 흐름: 플래그, 테스트, 첫 README
0606-02 – 06-078018eec8..41d6a7e8하스켈이 되어 가기하스켈이 되어 가기: 모듈, 프렐루드, 컴프리헨션
0707-01 – 07-1841d6a7e8..a824a175셀프 호스팅 총력전셀프 호스팅 총력전: "compile with uhs"
0807-18 – 07-20a824a175..298c9824부트스트랩부트스트랩: 컴파일러가 스스로를 컴파일하다
0907-20 – 08-10298c9824..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)까지 — 을 자세히 읽어 나가는 것이다. 의도적으로 거기서 멈춘다.