타입 추론이 가독성을 해치고, 특히 OCaml에서 디버깅을 어렵게 만들며, 연구의 초점을 잘못 돌린다고 비판한다. 타입에서 코드를 유도하는 발상이 더 타당하다고 주장한다.
타입 추론은 나쁘다. 코드를 읽기 어렵게 만들고, 이를 지나치게 사용하는 언어는 코드를 쓰기도 더 어렵다. 오늘 눈에 보이지도 않는 몇 밀리초의 타이핑을 아끼는 대신, 나머지 모든 것을 더 나쁘게 만드는 거짓된 절약일 뿐이다.
LSP가 잘 동작하는 IDE가 있다면, 변수 위에 커서를 올려 타입을 볼 수 있다. VSCode에서 rust-analyzer를 쓰면 타입 주석이 흐릿한 오버레이로 보인다—떠나간 타입들의 유령처럼.
하지만 코드를 읽는 맥락은 이외에도 많다: 책, 블로그 글, Git diff. 제한된 환경에서 코드를 편집할 때도 있다. 예를 들어 VM의 순정 vim.
그런 모든 맥락에서 코드는 덜 명확해진다.
Rust와 Haskell에서는 최소한 함수의 매개변수 타입과 반환 타입을 주석으로 명시해야 한다. 타입 추론은 함수 본문 내부의 변수 바인딩에만 적용된다. 이쪽이 훨씬 다루기 쉽다.
OCaml의 타입 추론은 훨씬 강력하다. 함수 시그니처를 주석으로 달 필요가 없다. 그리고 요구되지 않으니, 안 하게 된다.
문제는 타입 추론 엔진이 당신이 작성한 코드가 올바른지, 버그가 있는지 알지 못한다는 것이다. 그냥 주어진 것을 받아 제약 해결을 수행할 뿐이다.
그래서 내가 OCaml을 쓸 때 자주 실수를 하면, 컴파일러는 기꺼이 그 실수를 위로 전파하고, 실제 오류가 발생한 위치와는 아주 먼 곳에서, 도무지 이해하기 힘든 오류를 뱉는다. 거기에는 내가 감을 잡기 힘든 타입들이 얽혀 있다.
결국 모듈 안의 함수 시그니처에 타입 주석을 하나씩 추가하고 다시 컴파일하는 작업을 반복해 버그를 몰아세워야 한다. 그러면 에러 메시지는 실제 오류가 있는 코드 쪽으로 조금씩 가까워진다. 정말 심한 경우에는 개별 변수 바인딩에도 타입을 주석으로 달아야 한다. 그리고 나서야 오류를 찾는데, 예컨대 커리된 함수에 인자를 하나 더 넘겼다든가 하는 식이다. 하지만 타입 추론은 제약을 전파하는 과정이기 때문에, 추론이 포기하는 지점, 즉 에러 메시지가 발생하는 위치가 실제 실수로부터 임의의 거리만큼 멀리 튕겨 나갈 수 있다.
결국 타입은 어차피 쓰게 된다. 다만 훨씬 더 짜증나고 흐름을 끊는 방식으로.
새로운 타입 시스템을 소개하는 논문을 가끔 읽는다. 보통 동기(왜 이것을 신경 써야 하는가?) 설명은 한 페이지도 채 안 되고, 곧바로 추론 규칙이 나오며, 이어서 그 타입 시스템을 위한 타입 재구성 알고리즘을 설명하는 수많은 페이지가 붙는다.
타입 추론 알고리즘(또는 그것이 불가능하다는 증명)이 없으면 마치 숙제를 안 한 것처럼 여겨져 논문의 정당성이 떨어진다는 인식이 널리 퍼져 있다고 생각한다.
이는 소중한 자원(학술지의 지면)을 타입 주석을 달아야 한다는 가짜 문제를 해결하는 데 낭비하게 만든다. 그 지면은 타입 시스템의 응용을 설명하거나, 타입 시스템이 어떻게 동작하는지 이해를 돕는 코드 예제를 보여주는 데 쓰였어야 한다. 대신 우리는 논문이 임의로 고른 겐첸 표기 변종으로 적어 내려간, 해독하기 어려운 추론 규칙 페이지들을 마주하게 된다.
나는 코드에서 타입을 추론하고 싶지 않다. 오히려 타입에서 코드를 유도하고 싶다. 타입은 스펙이다. 작고 표현력도 낮다. 반면 코드는 크고, 타입보다 자유도가 사실상 무한하다. 버그의 표면적은 타입 쪽이 더 작다.
그러니 타입(단순하고 간결하며 제약된 것)을 사용해 코드를 생성하는 편이 타당하다(크고 제약이 적으며 작성에 시간이 걸리고 버그가 생기기 쉬운 것). 코드에서 타입을 추론하는 것은 설계도 없이 복잡한 기계를 만든 다음, 물리적 물체로부터 설계도를 뽑아내려고 X선 회절계로 들여다보는 것과 같다.