records 로 도메인을 쓰다

지금까지의 probe 는 모두 알고리즘이나 바이너리 형식이었고, 유일하게 재지 않은 축이 업무 도메인을 record 로 모델링하는 손맛이었다. money · account · entry · transaction 의 record 형과 많은 중첩 갱신을 갖는 복식부기 장부는 좋은 스트레스 테스트이고, 뻔히 숨어 있던 parser 의 구멍을 드러냈다. Mere 는 소문자 형 이름과 대문자 생성자라는 ML 관례를 따르고 record 선언도 그것을 지켰다 —— 그러나 record 리터럴은 형 이름이 대문자일 때만 parse 되어, 소문자면 변수 + block 으로 떨어져 원인에서 먼 오류로 실패했다. 수정은 한 분기 —— 등록된 record 이름(대소문자 무관)에 중괄호가 이어지면 record 리터럴이다. 그 이후 도메인은 깔끔히 모델링되었고, 정직한 거스러미가 하나 남았다.

mererecordstype-systemparsingdogfood

probe 는 모두 같은 쪽으로 기울어 있었다 —— 알고리즘, 수치 커널, 바이너리 형식 —— 그리고 한 번도 건드리지 않은 것이, 대부분의 소프트웨어가 실제로 그러한 가장 평범한 종류의 프로그램, record 로 도메인을 모델링하는 일이었다. 정교한 자료 구조가 아니라 의미 있는 필드를 가진 record 형 한 줌을, 구성하고 갱신하고 읽는다. 복식부기 장부는 그 형태의 응축판이다. money, account, entry, transaction 이 각각 record 이고, postings 가 잔액을 옮김에 따라 중첩 갱신이 일어난다. 그것을 쓰는 것이 probe 였고, 요점은 동작하느냐보다 어떻게 느껴지느냐 —— 그리고 어디서 쓸리느냐 —— 였다.

ML 관례의 구멍

쓸린 것은 즉시, parser 에서였다. Mere 는 형 이름이 소문자이고 생성자가 대문자라는 ML 전통을 따른다 —— type 'a list = Nil | Cons of .... 그렇게 선언된 record 형은 불평 없이 받아들여진다. type addr = { city: str } 는 유효한 선언이다. 그러나 record 리터럴 —— addr { city = "kyoto" } —— 은 형 이름이 대문자로 시작할 때만 parse 되었다. 중괄호 머리의 소문자 이름은 일반 케이스로 떨어져 변수에 이어지는 block 으로 읽혔고, “expected ‘;’ or ‘}’ in block” 로 실패했다 —— block 문법에 관한 오류로, 실제 문제의 어디도 가리키지 않는다. 문제는 parser 가 소문자 record 생성자를 인식하지 못하는 것이었다. variant · list · option 은 소문자 형 이름으로 늘 동작했다. record 만이 관례에 구멍이 있었다. record 리터럴 판정이 앞머리 대문자를 검사했기 때문이다.

하나의 분기

수정은 작았고, 오파싱이 일어나는 바로 그 자리에 있었다. “소문자 이름은 변수” 로 떨어지기 전에, 그 이름이 등록된 record 형인지(대소문자 무관) 확인하고, 다음 토큰이 여는 중괄호면 record 리터럴을 parse 한다. 그 분기를 넣으니 record 의 어휘 일습이 대소문자와 무관하게 동작했다 —— 구성, 필드 접근, 중첩 갱신, 하나의 깊은 필드만 바꿔 record 를 다시 짜는 저 보기에 성가신 { p | home = { p.home | city = ... } } 를 포함해서. 수정이 하나의 분기였다는 것이 설계가 옳았고 parser 만 뒤처졌다는 증거다. 형 시스템도 의미론도 움직일 필요가 없었고, 그저 한 자리에서 소문자 이름을 인식하기만 하면 됐다.

남은 거스러미

거스러미 하나는 다듬어지지 않았다. 그것은 다른 곳에서도 나타나는 같은 규칙이고, 사실 버그가 아니기 때문이다. 갱신되는 record 매개변수는 형을 주석해야 한다. 주석이 없으면 함수는 그 매개변수에 대해 다형이고, 갱신 자리에는 갱신할 record 형이 없다 —— 오류는 “record update base must be a record value” 다. 이것은 numeric 오버로드가 요구하는 것과 같은 요구로, 산술로 흘러드는 다형 매개변수는 연산자가 어느 형을 다루는지 알 수 있도록 주석되어야 한다. base 가 구체적인 필드 접근인 중첩 갱신은 문제없다. 맨 다형 매개변수만이 힌트를 필요로 한다. 그것을 고치지 않고 선 채로의 거스러미로 기록하는 것은 의도적인 선택이다 —— 회피는 주석 하나, 진짜 수정은 더 큰 추론 변경이고, pain 이 아직 그것을 정당화하지 않는다. 장부는 균형을 이뤘고, revenue 가 cash 와 1 의 자리까지 일치했으며, 소문자 구멍이 닫힌 지금 언어의 record 쪽은 잘 읽힌다.

← Back to Mere: 언어를 만들다