순서의 나머지 절반

셀프 호스트의 fixpoint는 정상이었지만, 정상은 떠나는 곳이다. 내려가는 첫걸음은, 여러 Part 전에 소리 내어 이름 붙인 부채를 갚는 것이었다: 언어는 임의의 두 값을 구조적 등가로 비교할 수 있었지만, 순서 매길 수는 없었다. 그것을 끝내는 것 —— 구조적 등가 곁에 구조적 less-than을 —— 으로, trait 시스템을 일절 세우지 않고 길러 온 거동의 한 가족이 완성되었다. 게다가 그것은, 그 가족이 이미 만들어진 바로 그 기구를 재사용해 이루어졌다.

merederiveorderingpolymorphismlanguage-design

정상은 목적지가 아니다; 돌아서 내려가기 시작하는 곳이다. 앞 Part는 그 하나에서 끝났다 —— bootstrap fixpoint, 컴파일러가 자기 자신을 바이트 단위로 동일한 복제로 컴파일하는 것 —— 그리고 이 Part는 그 너머의 땅에 대해서다. 거기서 작업은, 컴파일러가 자기 자신에 대해 무언가를 증명하는 것에서 떠나, 언어가 무엇을 할 수 있고 무엇을 아직 못 하는가, 로 돌아갔다. 내려가는 첫걸음은 작고 구체적이었고, 그리고 훨씬 전 Part부터, 이름 붙여져 기다리고 있었다.

소리 내어 이름 붙인 부채

여러 Part 전, 도출된 거동의 한 가족이 세워졌을 때 —— 값을 텍스트로 그리기, JSON으로 직렬화하고 되돌리기, 두 값을 구조적 등가로 비교하기, 모두 trait 시스템 없이 컴파일 타임에 타입마다 특수화 —— 한 멤버가 빠졌다고 평명히 진술되었다. 언어는 두 값이 같은가를 그 형태를 걸어 물을 수 있었지만, 어느 쪽이 작은가는 물을 수 없었다. 구조적 등가는 있고, 구조적 순서는 없었다. 나중에 세운 수치 도구가 그 gap에 정면으로 부딪혔다: 비교를 손으로 적음으로써만 list를 정렬할 수 있었다, 왜냐하면 비교 연산자는 수와 문자열에는 효해도, 실의 데이터셋이 이루어진 tuple이나 record에는 효하지 않았기에. 가장자리는 열린 채, 숨기는 게 아니라 미건으로 적혀 있었다 —— 프로젝트 전체의 규율이 “프로그램이 그 부재를 구체적으로 만들 때까지 미룬다”였기에 —— 그리고 이제, 하나가 구체적으로 만들었다.

가족을, 그 가족 자신의 부품으로 완성한다

기분 좋은 것은, 그것을 끝내는 데 새 기구가 필요 없었다는 것이다. 짝을 결한 등가는, trait으로도 사전으로도 runtime lookup으로도 구현되어 있지 않았다; 타입 검사기가 각 사용에서 구체 타입을 알고, 코드 생성기가 그 타입을 위한 올바른 비교를, inline으로, 컴파일 타임에 emit함으로써 구현되어 있었다. 순서는 꼭 같은 틀에 끼워진다. less-than과 그 세 친척이, 이제 임의의 구체 타입에서 효한다: 정수와 부동소수점 수와 문자열은 직접 비교하고, tuple·record·list·variant는 사전식으로 비교한다 —— 성분마다, 선언순 필드마다, 요소마다, 그리고 variant는 먼저 선언순으로, 다음에 내용으로. 저마다가 타입마다의 작은 비교 함수로 emit된다, 이미 emit되고 있던 등가 함수의 순서 판으로. 하나의 이름, 많은 타입 고유의 거동, 정적으로 골라지는 —— 그림과 JSON과 등가가 가졌던 것과 같은 형태, 이제 비교가 완성되어.

「작다」의 의미를 backend 간에 일치시킨다

유일하게 정말 새로운 결정은, 언어가 순서를 건네주지 않는 곳에서 어떤 순서를 부과하는가, 였다. 수와 문자열에는 명백한 순서가 있다; variant 타입에는 없다. Red | Green | Blue에 내재하는 랭킹은 없으므로 선택이 필요하고 —— 그리고 여기가 요점 —— interpreter와 두 컴파일 backend가 같은 선택을 해야 한다, 아니면 값이 interpreter에서는 한쪽으로, native 바이너리에서는 다른 순으로 정렬된다. 골라진 순서는 선언순: 먼저 쓰인 constructor가 가장 작다. 그것은 발견이 아니라 선택이지만, 전순서로 결정적이고, interpreter는 코드 생성기가 constructor를 번호 매기는 것과 같은 소스에서 그것을 재구성하므로, 셋이 바이트 단위로 일치한다. 그 일치는, 프로젝트 전체가 이런 종류를 확인하는 방식으로 확인된다 —— 같은 값을 세 방식으로 정렬해 동일하게 나옴을 요구한다 —— 주장이 아니라.

남은 가장자리

가족을 완성해도 모든 것이 닫히지 않았고, 정직한 것은 어느 구석이 남았는지를 말하는 것이었다. 타입 주석 없이 쓰인 비교는 operand를 정수로 default하므로, 편리한 한 줄 정렬 —— comparator를 전혀 취하지 않는 것 —— 은 여전히 정수 전용이다; 그 외의 list를 정렬하려면 주석 붙은 비교를 적고 싶어진다. 완전히 일반적인 판, 언어가 「순서 매길 수 있음」을 제약으로 추론하고 요소 타입이 안착하는 곳에서 해결하는 판은, 다른, 더 큰 기계 —— 진짜 ad-hoc 다상 해결 —— 이고, 이전과 같은 규칙 뒤에 미뤄진 채: 프로그램이 그 부재를 견딜 수 없게 만들 때까지. ship한 것은 구조적 비교 그 자체로, 모든 구체 타입에서 모든 backend에 걸쳐 효한다. 그것이 값의 대부분이고, 새 기계는 영이다.

이것은 정상에서 내려가는 의도해 겸손한 첫걸음이었다 —— 부채를 갚고, 가족을 완성하고, 앞 Part의 약속을 열린 채 지켰다. 다음에 온 것은 겸손하지 않았다. 언어가 이미 가진 것의 정리를 끝내고, 작업은 바깥으로, 전혀 할 수 없는 것으로 향했다 —— 그리고 그것들을 찾는 길은, 짐작하는 게 아니라, 무언가 실의 것을 세웠을 때 아픈 곳을 찾으러 가는 것이었다.

← Back to Mere: 언어를 만들다