Mere는 무엇을 최적화하는가
언어 설계는 모두 '무엇을 최적화할 것인가'에 대한 베팅이다. Mere의 베팅은 올바름의 검증 가능성 —— 암묵적인 것을 명시적으로 만들어, 프로그램을 읽으면 그것이 무엇을 하는지 알 수 있게 하는 것. 왜 그 원칙인가, '암묵적'이란 정확히 무엇을 가리키는가, 그리고 코드를 AI와 함께 쓴다면 장황함은 치를 만한 대가인 이유.
모든 프로그래밍 언어는, 대부분의 언어가 소리 내어 말하지 않는 물음에 대한 답이다 —— 우리는 무엇을 최적화하고 있는가? 쓰는 속도, 실행 속도, 안전성, 단순함, 익숙함. 이 모두를 동시에 최대화할 수는 없으므로, 언어란 사실 순위 매기기다. 언어 간의 흥미로운 차이는 대부분 구문이 아니라 이 순위에서 나온다.
Mere의 순위는 하나를 정점에 둔다:
올바름의 검증 가능성.
올바름 그 자체가 아니라 —— 그것을 보장하는 언어는 없다 —— 당신이, 혹은 도구가, 프로그램이 주장하는 대로 동작함을 얼마나 쉽게 검사할 수 있는가이다. 설계상의 다른 모든 것은 이에 봉사하는 한에서 협상 가능하다.
암묵적인 것을 명시적으로
검증 가능성을 위한 주요 지렛대는, 단순하게 들리지만 실은 거의 모든 것에 닿는 규칙이다:
런타임 표현이나 동작이 조용히 바뀔 수 있는 것은 명시한다.
여기서 일하는 단어는 조용히 다. 전형적인 언어가 “생각하지 않아도 된다”고 해 주는 항목들을 보자:
- 메모리. 이 값은 스택에 있는가, 힙 할당인가, GC 대상 객체인가, 한꺼번에 해제되는 아레나 안인가? 대부분의 언어에서는 사용 지점에서 보이지 않으며, 코드가 자라면서 바뀐다.
- 이펙트. 이 함수는 파일을 읽는가, 공유 상태를 바꾸는가, 로그를 남기는가, 메모리를
할당하는가? 밋밋한
T -> U시그니처는 아무 말도 하지 않는다. 이펙트는 실재하는데도 주변적(ambient)이다 —— 타입이 언급하지 않는 경로로 드나든다. - 스레드 간 공유. 이 값을 다른 스레드에 넘겨도 되는가? 대부분의 언어에서 타입은 침묵하고, 답은 “런타임에, 혹은 디버거에서 확인하라”다.
이들은 모두 프로그램의 실제 동작이 타입이 말하는 내용에서 벌어지는 지점이다. Mere의 입장은 그 틈을 닫는 것이다: 메모리 소유권, 이펙트, 그리고 (시리즈가 동시성에 이르면) 어떤 값이 스레드 경계를 넘을 수 있는지, 이 모두를 타입 시스템이 말하는 대상으로 만든다. 주석이나 관례를 믿을 필요가 없다. 시그니처를 읽는다.
그 보상이야말로 이 언어의 핵심이다: 코드 한 조각을 보고 그것이 무엇을 하는지 알 수 있다 —— 무엇을 건드리고, 무엇을 소유하며, 뒤에서 무엇을 할 수 있는지 —— 왜냐하면 “뒤” 가 없기 때문이다.
왜 장황함은 치를 만한 대가인가
암묵적인 것을 명시적으로 만드는 데에는 분명한 대가가 있다: 쓸 것이 늘어난다. 조용히 로그를 남기고 메모리를 할당하던 함수는 이제 그것을 타입에 적는다. 리전에 사는 값은 리전을 타입에 지니고 다닌다. 이것이 바로 인간 우선 언어를 반대 방향 —— 추론, 기본값, 주변적 문맥 —— 으로 미는 대가다. 사람이 그것을 전부 타이핑해야 하기 때문이다.
Mere는 다른 베팅을 한다. 그리고 그것이 이 설계 전체를 가능하게 하는 베팅이다:
코드는 AI의 도움으로 쓰므로, 장황함은 싸다.
“타이핑이 많다”는 것이 더 이상 제약이 아니게 되면, 트레이드오프는 뒤집힌다. 언어는 이제 쓰는 맛을 유지하려고 소유권이나 이펙트를 숨길 필요가 없다. 그것들을 드러낼 수 있다. 적어 내는 마찰이 거의 사라졌기 때문이다. 여전히 비싼 것은 —— 사람에게도 도구에게도 —— 꿰뚫어 볼 수 없는 프로그램에 대해 추론하는 것 이다. Mere는 장황함을 지불해 투명성을 산다.
이것은 “보일러플레이트는 많을수록 좋다”가 아니다. 코드를 기계와 공동으로 쓰는 이상, 남은 예산을 프로그램을 짧게 만드는 데가 아니라 검사 가능하게 만드는 데 쓴다는 것이다.
타협은 피할 수 없다 —— 그러니 “어디서”를 고른다
트레이드오프가 없다고 주장하는 설계는 그것을 숨기고 있다. 검증 가능성을 최적화하는 데에는 실제 대가가 있다: 어떤 프로그램은 더 장황해지고, 주변 상태에 기대는 패턴은 쓰기 어려워지며, 몇몇 편의는 의도적으로 포기한다. Mere의 규칙은 그것을 얼버무리는 것이 아니라, 그때마다 트레이드오프를 명시하고 의도적으로 고르는 것이다.
이후의 회차는 대부분 그 선택들의 기록이다:
- GC가 아니라 리전/뷰 메모리 모델 —— 명시적 수명, 대가는 타입 구조의 증가.
- 주변적 I/O가 아니라 이펙트의 능력 전달 —— 시그니처에 보이는 이펙트, 대가는 호출을 통한 능력의 전달.
- “아마 괜찮겠지”가 아니라 동시성의
Send/Sync—— 타입 검사기가 배제하는 데이터 경쟁, 대가는 술어 몇 개 더.
어느 경우든 타협은 그것이 이루어지는 자리에서 명시된다. 그래서 독자는 Mere가 예산을 잘 썼는지 판단할 수 있다.
영리하지도, 빠르지도 않게 —— 읽어낼 수 있게
이 중 무엇도 영리함의 이야기가 아니며, 이런 프로젝트가 부추기기 쉬운 것과는 달리, 만드는 것이 빠르다는 이야기도 아니다. 목표는 더 좁고, 그리고 —— 나는 더 유용하다고 생각한다 —— 읽으면 신뢰할 수 있는 프로그램 이다. 진실을 말하는 시그니처. 동작이 바뀌면 바뀌는 타입. 주장을 검증할 수 없는 프로그램을 거부하는 컴파일러.
시리즈의 나머지는 이 원칙을 하나씩 적용한 것이다: 최소 코어, 다음에 메모리, 다음에 이펙트, 다음에 네 백엔드, 다음에 언어가 자기 자신을 컴파일하는 것, 다음에 동시성. 어느 파트든 같은 물음을 다시 묻고 있다 —— 이것을 어떻게 검사 가능하게 만들 것인가? —— 그리고 그것에 어떻게 답했는지의 기록이다.
다음 회: 그 모든 것을 만들 호스트 언어를 고른다.