trait가 주는 것을, trait 없이
어느 dogfood 애플리케이션도 같은 보일러플레이트를 손으로 썼다: 값을 어떻게 표시하는가, 둘을 어떻게 비교하는가, record를 JSON으로 어떻게 바꾸고, 어떻게 되돌리는가. 그 보일러플레이트야말로 trait나 typeclass 시스템이 없애기 위해 존재하는 것이다 —— 하지만 그것을 세우는 것은, 해결, coherence, 사전 전달, 명시적임을 존중하는 언어와 어울리지 않는 기구 통째를 의미한다. Mere는 더 좁은 길을 택했다. 이미, trait를 어디에도 갖지 않고 타입으로 dispatch하고, 완전히 컴파일 타임에 해결되는 polymorphic builtin을 하나 갖고 있었고, 시스템을 세우는 대신 그 씨앗에서 한 가족을 길렀다. 그 가족의 가장 흥미로운 멤버는, 전혀 설계되지 않았다 —— 적대적 입력을 받는 dogfood가, 그 정확한 형태를 강요했다.
어느 dogfood 애플리케이션도 손으로 쓰고, 또 쓰고, 쓰기를 싫어한 종류의 코드가 있다. 로그 줄을 위해 값을 텍스트로 그리는 방법. 두 값이 같은지 정하는 방법. record를 JSON으로 바꾸는 방법, 그리고 더 나쁘게, JSON을 record로 되돌리는 방법, 필드마다, rename 때마다 불일치를 동반하며. database 라이브러리는 손으로 쓴 encoder와 decoder로 두툼했다. 이 보일러플레이트는 우발적이지 않다; 그것은 바로, trait 시스템과 typeclass가 폐지하기 위해 발명된 것이다. 그래서 하나 세울 압력은 한동안 높아지고 있었고, 그것에 답할 때가 왔다.
세우고 싶지 않은 시스템
곤란한 것은, trait 시스템이 크고 주장이 강한 기계라는 것이다. 그것은 각 사용에서 어느 구현이 들어맞는지를 해결하는 방법, 두 import가 타입의 표시 방식으로 불일치할 수 없게 하는 coherence의 규칙, 그리고 대개 runtime의 장치 —— 값 곁에 보이지 않게 전달되는 사전 —— 를, 선택된 구현을 그것이 도는 곳으로 나르기 위해 요한다. 그 모두가 실행 가능하고, 그 모두가, 언어가 놓기를 거부해 온 하나에 반해 잡아당긴다: runtime에서 일어나는 일은 소스에서 읽어낼 수 있어야 하고, 등 뒤로 threaded되는 보이지 않는 인자 따위 없다, 는 것. trait의 이전 지상 연구가 바로 이 결론에 다다랐고, 아이디어 통째를 dogfood가 아픔을 피할 수 없게 만들 때까지 defer로 분류했었다. 아픔은 이제 도래했다. 물음은, 풀 기계가 그것과 함께 도래해야 하는가, 였다.
이미 거기 있었던 씨앗
그렇지 않았다, 왜냐하면 언어는 같은 문제로의 더 작은 답을, 평명히 숨어, 이미 담고 있었기 때문이다. 임의의 값을 텍스트로 그리는 builtin은 이미 polymorphic이었다 —— 정수, list, record, variant에 적용할 수 있었다 —— 그러면서도 소스 어디에도 그것을 위한 trait도, instance 선언도, 사전도 없었다. 그것이 기능한 것은, 타입 추론기가 그것이 쓰이는 모든 곳에서 구체 타입을 이미 알고 있었고, 코드 생성기가 그 특정 타입을 위한 올바른 그림을, inline으로, 컴파일 타임에 emit했을 뿐이기 때문이다. 하나의 이름, 많은 타입 고유의 거동, 정적으로 해결되어 생성 코드에 명시된다. 그것이 trait가 주는 것의 대부분으로, trait 기구의 어느 것도 없이 달성된다 —— 그리고 그것은 줄곧 언어 안에, 흔한 builtin으로 앉아 있었다.
그래서 한 수는 시스템을 세우는 게 아니라, 그 씨앗에서 한 가족을 기르는 것이었다. 값이 컴파일 타임 특수화로 텍스트로 그려질 수 있다면, 같은 방식으로 JSON으로 그려질 수 있다; 구조적인 JSON encoder가 가족에 더해져, record를 타입명을 떨궈 object로, list를 array로, option을 그 내용이나 null 중 하나로 바꿨다. 구조적 등가도 더해져, 한 타입의 두 값을 그 형태를 걸어 비교했다. 새 멤버는 저마다, 새 기구를 도입하는 게 아니라 기존의 특수화 path를 재사용했고, 그것이 연재 전체가 도는 규율이다: 새 문제가 나타났을 때, 더 발명하기 전에 이미 세운 기구를 확장한다.
하나의 정직한 비대칭
encoder에는 자연스러운 짝이 있었다 —— decoder, JSON 텍스트를 typed 값으로 되돌린다 —— 그리고 그것을 세우는 것은, 정직할 값이 있는 genuine한 비대칭을 surface했다. encoder는 건네받은 값을 보고 그 값의 타입으로 dispatch할 수 있다. decoder는 못 한다: 그것이 건네받는 것은 문자열이고, 문자열은 거기서 무엇을 얻고 싶어 하든 문자열이다. 그래서 decoder는 대상의 타입을 인자가 아니라 call site에서, 무엇을 향해 디코드하는가를 말하는 annotation에서 취한다. 그 타입이 주어지면, 구조적인 일을 거꾸로 한다 —— JSON object는 필드명을 대조해 record로, array는 list나 tuple로, 값이나 null은 option으로, 태그 붙은 형태는 variant가 된다. 그것은, 정보가 다른 방향에서 와야 하는 한 곳을 빼고 모든 점에서 encoder의 거울이고, 그것을 평명히 말하는 것은 대칭이 완벽한 척하는 것보다 낫다.
dogfood가 설계한 멤버
가족의 가장 시사적인 멤버는 지상에서 결코 설계되지 않았다. decoder는, 처음 세워졌을 때, fail-fast였다: 불정한 입력을 건네면 abort한다 —— interpreter에서는 예외, native 바이너리에서는 프로세스가 exit한다. 그것은 trusted한 입력에게 올바른 거동이다: 설정 파일, 내부 데이터 파일, 테스트 fixture, 거기서 불정 입력은 큰 소리로 듣고 싶은 버그다. 그런 뒤 blog 애플리케이션이, 결코 신뢰해서는 안 되는 유일한 것 —— 도래하는 web 요청의 body —— 에 그것을 쓰려 했다. 적대적 입력에 대한 fail-fast한 decoder는 버그를 보고하지 않는다; 하나의 불정한 요청으로 서버 통째를 쓰러뜨린다. 그것은 security의 퇴행이지, 편리함이 아니다.
그래서 두 번째 decoder가 존재를 강요받았다, abort하는 대신 optional한 값 —— 실패 시엔 없음 —— 을 돌려주는 것. 그것은 어느 로드맵에도 없었다; dogfood가, 실의 네트워크에 노출된 실의 서버임으로써, 그것을 요구했다. 그리고 그것은, 이제 언어의 조언의 일부인 명쾌한 선을 그었다: abort하는 decoder는 신뢰하는 입력을 위해, optional한 쪽은 신뢰하지 않는 입력을 위해. blog의 signup과 login과 게시가 typed한 요청 record를 optional decoder에 통과시키는 형태로 다시 쓰였고, 수정은 그것이 중요한 곳에서 검증되었다 —— 정상 body는 생성된 리소스를 낳고, 불정한 것은 클라이언트 에러를 낳고, 그리고 가장 중요하게, 서버는 그 뒤에도 아직 살아 다음 요청에 올바로 응한다. loop는 실의 database 상대로, native 바이너리로, gap을 노출한 바로 그 조건 아래에서 닫혔다.
가장자리를, 평명히
설계되는 게 아니라 길러진 가족에는 가장자리가 있고, 유용한 것은 그것들을 숨기는 게 아니라 이름 짓는 것이다. option을 벌거벗은 값이나 null로 encode하는 것은 idiomatic이고 깨끗이 round-trip하지만, 부재의 값과, 우연히 빈 존재하는 값을 구별할 수 없다 —— 실용상 드물고, 그러면서도 실의 nested-option의 구석. decoder는 record의 모든 필드가 존재하기를 요하므로, 진짜 optional한 키는 아직 표현할 수 없고, 미래의 finding으로 분류된다. 그리고 가족은 한 번에 전 backend에 착지하지 않았다: decoder는 interpreter와 native path에 먼저 갔고, 그것을 동기 부여한 바로 그 애플리케이션의 브라우저 배포를 일시적으로 깨뜨렸고, WebAssembly 판이 다음 릴리스에서 그 부채를 갚고 backend를 수평으로 하기 위해 뒤따랐다. 이것들 중 어느 것도 feature의 기술 방식 안에서 숨겨지지 않는다, 왜냐하면 보증은 그 진술된 한계가 값 매기는 것만큼의 값이기 때문이다.
아크 통째가 산 것은 trait 시스템이 파는 편리함 —— 더는 손으로 쓴 printer도, comparator도, encoder도, decoder도 없다 —— 을 trait 시스템 자신 없이: 해결 없음, coherence의 규칙 없음, 사전 없음, 프로그램을 통해 보이지 않게 threaded되는 것 없음. 그것은, 하나의 기존 builtin이 이미 어려운 부분을 풀고 있었음을 알아차리고, 그 닮은꼴로 가족을 기르는 것에서 왔다; 그리고 그 가장 날카로운 feature는, 설계자의 선견에서가 아니라, 열린 곳에 서서 맞은 dogfood에서 왔다.