최소한의 ML 코어

리전도, 이펙트도, 네 백엔드도, 셀프 호스팅도, 그 전에 Mere는 먼저 실제로 동작하는 언어여야 했다. 최소한의 진짜 코어 —— Hindley–Milner 추론, 대수적 데이터 타입, 패턴 매칭, REPL —— 그리고 왜 최소에서 시작하는가. 그리고 명시성을 자랑하는 언어에서 추론된 타입이 어떻게 어우러지는가.

meretype-inferencehindley-milnerreplcompiler

Mere의 야심 찬 부분 —— 명시적 메모리 모델, 능력으로서의 이펙트, 네 백엔드, 브라우저 에서의 자기 컴파일 —— 은 모두 무언가에 붙는다. 그 무언가란 작고 평범한 ML 코어이며, 흥미로운 아이디어 그 어느 것보다 먼저 그것을 만든 것은 의도한 선택이었다.

실제 언어인 최소의 것

첫 목표는 종이 위에서는 대단해 보이지 않았다: 프롬프트에 입력하면 답이 돌아오는 언어. 리터럴과 산술, let, 함수와 적용, if. let rec fact = fn n -> if n < 1 then 1 else n * fact (n - 1) in fact 6 라고 쓰고 720 이 보이는, 그 정도면 충분하다.

그 답을 만드는 파이프라인이, 다른 모든 것이 자라나는 척추다:

소스 → 어휘 분석 → 구문 분석 (AST) → 추론 (타입) → 평가 → REPL

이후의 어느 장도 이 단계 중 하나의 확장이다. 리전과 이펙트는 타입을 확장한다. 네 백엔드는 “평가”를 “코드 생성”으로 바꾼다. 셀프 호스팅은 척추 전체를 Mere 자신으로 다시 쓴다. 척추를 올바르게, 동작하는 형태로 만드는 것 —— 아무리 작더라도 처음부터 끝까지 —— 이, 그중 무엇을 야심 차게 만드는 것보다 먼저였다.

그다음, 계산기를 ML로 바꾸는 기능이 왔다: 대수적 데이터 타입패턴 매칭, 튜플, 그리고 다형 타입 —— 'a option, 리스트, 트리. 이것들은 언어의 나머지가 조립되는 재료의 형태이므로, 일찍 존재해야 했다. 자기 자신의 데이터를 표현하고 매칭할 수 없는 컴파일러는, 키울 수 없는 컴파일러다.

명시성을 중시하는 언어에서의 추론

정면으로 마주할 가치가 있는 긴장이 있다. 첫 회에서 Mere는 암묵적인 것을 명시적으로 만든다고 했다. 이번 회는 Hindley–Milner 타입 추론 을 더한다 —— fn n -> n + 1 이라고 쓰면, 당신이 말하지 않아도 컴파일러가 int -> int 를 이끌어내는 기계다. 타입을 추론하는 것은 명시적임의 반대가 아닌가?

그렇지 않다. 그리고 그 구별이야말로 요점이다. Mere가 명시적으로 만드는 것은, 런타임 표현이나 동작이 조용히 바뀔 수 있는 것 —— 메모리 소유권, 이펙트, 스레드 넘나듦. 모든 바인딩에 int 를 쓰게 하는 장사를 하는 게 아니다. 평범한 타입은 추론으로 재구성된다. 그것들은 애초에 위험한 동작을 숨기는 부분이 아니었다. 첫 회의 장황함 예산은, 검증 가능성을 사는 곳 —— 이펙트와 메모리 —— 에 쓰고, 기계가 쉽게 복원할 수 있는 표기에는 쓰지 않는다.

그러니 추론과 명시성은 대립하지 않는다. 같은 방침을 두 측면에서 본 것이다. 추론해도 안전한 것은 추론한다. 그렇지 않은 것은 드러낸다. HM은 전반부를 공짜로 준다. 바로 그것이 코어에 속하는 이유다: 평범한 부분을 조용히 유지해, 명시적인 부분을 도드라지게 한다.

구체적으로는 let 다형성을 동반한 HM이다: let 로 묶인 함수는 일반화되므로, let id = fn x -> x 는 한 곳에서 int, 다른 곳에서 str 로 쓸 수 있고, 각 사용은 새로 구체화된다. 이것은 ML의 최소 조건이며, 이후 타입 시스템 작업 —— 리전, Send / Sync —— 이 그것과 싸우는 대신 위에 붙는 토대다.

왜 최소인가, 왜 먼저인가

작게 시작하는 것은 겸손이 아니다. 방법론이다. 프로젝트 전체는 설계상의 물음을 실제 프로그램을 돌려서 답하는 것 —— 도그푸딩 —— 으로 움직인다. 그리고 동작하는 코어가 없으면 아무것도 돌릴 수 없다. 순서는 강제된다:

  • 고차 코드에서 자신의 이펙트 설계가 어색하다는 것을 알려면, 먼저 고차 코드를 쓸 수 있어야 한다.
  • 리전 표기가 너무 시끄럽다는 것을 알려면, 사람이 (혹은 자신이) 실제로 쓰는 언어에 리전이 붙어 있어야 한다.
  • 네 백엔드를 서로 대조해 검증하려면, 넷 모두가 구현할 언어가 있어야 한다.

그러니 최소 코어는 시리즈의 나머지를 재는 계기다. 의도적으로 지루하게 만들었다: 단단하고 뜻밖이 없는 ML, 읽으면 올바름을 확인할 수 있는 종류의 것 —— 그것이, 첫 회대로, 핵심 그 자체다. 흥미로운 결정은 그것이 존재한 뒤에, 동작하는 언어만이 던질 수 있는 문제에 대한 답으로 온다.

그 코어가 자리 잡고 —— 추론, 데이터 타입, 매칭, 만져 볼 수 있는 REPL —— 야심 찬 부분이 시작된다. 첫 번째는, Mere가 그 주위에 만들어졌다고까지 할 수 있는 한 수: 메모리를 다루는 방식이다.

← Back to Mere: 언어를 만들다