Haskell이 이런 걸 타입으로 강제하는 데 가장 강력한 축에 드는 언어인 건 맞지만, 같은 패턴은 Rust와 TypeScript에서도 꽤 잘 먹힘
User -> LoggedInUser -> AccessControlledLoggedInUser 같은 흐름으로 웹앱에서 반복되는 명백한 권한 부여 버그를 막는 방식도 좋아함
업계에서는 이 패턴이 엄청나게 덜 쓰이고 있다고 봄
이건 Rust나 TypeScript에만 해당하지 않고, 사실상 거의 모든 언어에서 가능함
보안상 이스케이프 전/후 문자열을 구분해야 한다면 동적 타입 언어에서도 Escaped 클래스로 감싸고 escape(str)->Escaped, dangerouslyAssumeEscaped(str)->Escaped 같은 함수를 둘 수 있음
성능 비용이 있으니 절충은 필요하지만 가능함
또 다른 방식은 Application Hungarian이고, 다만 이건 컴파일러보다 프로그래머의 규율에 더 의존함: https://www.joelonsoftware.com/2005/05/11/making-wrong-code-...
이건 타입 시스템 자체보다 어포던스의 문제에 가까움
예를 들어 C#에서도 충분히 할 수 있지만, 실제 타입 정의보다 시각적 잡음이 더 커지는 식임
Rust와 TypeScript도 당연히 Haskell의 영향을 크게 받았음
다만 “모나드는 무서우니 튜토리얼을 써야겠다” 같은 효과를 피하려고 그걸 굳이 말하지 않고 이름도 다르게 부르는 편임
모나드보다는 타입 클래스 같은 쪽 영향이 더 큼
TypeScript에서 이게 정말 잘 된다고는 확신이 안 듦 명목 타입이 없어서 원시 타입을 감싸는 newtype 같은 걸 만들려면 꽤 해키한 주문을 기억해야 함
내 경험상 이런 타입 안전성을 강제하는 데는 OCaml이 Rust보다 더 강력했음
GADT로 표현력이 더 크고, 다형 변이와 객체 타입/레코드 행 타입으로 편의성도 있으며, 모듈 시스템과 펑터도 있음
가비지 컬렉션이면 충분한 영역에서는 Rust의 빌림 검사기 때문에 생기는 추상화 제약과 어려움도 피할 수 있음
몇 년간 Haskell로 일하는 걸 정말 좋아했음
일부러 찾던 건 아니었지만 기회가 우연히 왔고, 흥미롭고 지적으로 자극적이었음
다만 안타깝게도 Haskell만 3년을 쓴 뒤에도 Rust에서의 생산성이 Haskell의 쉽게 두 배는 됨
Haskell에는 미리 알고 피해야 하는 함정이 더 많고, 작성자에 따라 거의 읽기 전용 언어처럼 소화하기 어려울 때가 있음
도구 체인은 종종 Nix와 결합되어 있는데 Nix 자체도 복잡한 괴물이고, 언어 확장은 사방에 퍼져 있는 느낌임
Cabal 파일도 별로고, 컴파일러 오류에 익숙해지는 데 시간이 걸림
꽤 놀랍게도 내 경험은 거의 정반대였음
마지막 제품에서 백엔드를 Typescript에서 Rust로 옮기기 시작했는데, 크래시에 지쳐서였음
지금은 그걸 내가 저지른 가장 큰 기술적 실수 중 하나로 봄. 생산성이 엄청나게 느려졌기 때문임
Rust에서만 생긴 시간 낭비 예로는, 데이터베이스 연결을 열고 뭔가 한 뒤 닫는 식의 고차 함수 작성이 Haskell, TypeScript, JavaScript, C++, PHP에서는 사소한데 Rust에서는 Rust 전문가 친구들에게 물어봐도 사실상 불가능해서 포기하게 된 일이 있음
또 리팩터링을 시도해 하루 종일 타입 오류를 고치다가 최상위 파일에서 오류를 만나고, 알고 보니 설계의 기본 부분 때문에 전체 리팩터링이 불가능하다고 판단해 전부 되돌린 적이 여러 번 있음
게다가 Rust는 구체 타입 대신 인터페이스로 값 사용하는 일이 상황에 따라 고급 기법과 불가능 사이 어딘가에 있는, 내가 떠올릴 수 있는 유일한 현대 언어임
그래서 애플리케이션 코드, 즉 시스템 코드나 라이브러리 코드가 아닌 코드는 대략 Rust로 쓰면 안 된다는 결론에 도달함
생산성이 전반적으로 2배였는지, 아니면 Rust에서 덜 생산적인 부분도 있었는지 궁금함
그리고 “읽기 전용”이라는 건 무슨 뜻인지도 궁금함
일반적인 인식과는 다르게, Mercury가 Haskell을 선택했고 초기 리더들이 Haskell에서 풍부한 경험을 가졌다는 점이 성공에 적지 않은 역할을 했을 수 있다고 봄
Mercury 고객 입장에서 이 회사는 내 도구함의 핵심 회사 중 하나이고, Haskell 선택이 그들의 진행, 개발, 전체 여정을 더 좋게 만들었다는 느낌을 지울 수 없음
물론 대부분의 언어에 대해 이런 주장을 할 수 있고, Haskell 같은 함수형 언어가 성공 공식이라는 뜻은 아님
하지만 “vibe coding”과 LLM 시대 이전에 이런 의도적 결정을 한 건 특히 선견지명이 있어 보이고, 글에서 자세히 다룬 엔지니어링 문화와 결합된 결과라고 봄
오히려 성공 요인은 스타트업 지향 핀테크 초점과 실행력일 가능성이 큼
나도 좋은 기술 문화를 좋아하지만, 훌륭한 기술 문화를 가진 회사가 나쁜 사업 초점 때문에 죽는 걸 봤음
더 나아가 스타트업식 핀테크 문화가 좋은 기술 문화를 낳았을 수도 있음
은행으로 출발하지 않았기 때문에, 예컨대 SVB와 달리 그렇게 보수적일 필요도 없었고 끔찍한 고대 기술 스택과 통합할 필요도 없었음
Haskell로 성공한 건 기쁘지만 Jane Street와 OCaml처럼, 회사가 믿게 만들고 싶어 하는 것과 달리 언어 선택은 사업 측면에서는 거의 우연에 가깝다고 봄
다만 프런트엔드는 뭘 쓰는지 궁금함. 아마 이 Haskell은 전부 백엔드일 것 같음
해당 언어 경험이 없는 제너럴리스트 채용도 오히려 도움이 됐을 것 같음
새로 온 사람들에게 문화와 스타일을 처음부터 심을 수 있었기 때문임
vibe coding 이전이라면 그런 사람들 대부분은 아무 지시 없이 그냥 뛰어들어 해킹하려고 하지는 않았을 것임
앱의 모든 것이 그냥 잘 동작한다는 점을 느꼈음
다른 서비스에서 넘어오면 정말 만족스러움
절친이 이 회사에서 일하는데, 밖에서 보기에도 엔지니어링 문화가 좋아 보임
Haskell은 이 일에 맞는 도구이고 강점을 잘 살리고 있다고 보지만, 성공의 상당 부분은 그냥 회사가 전반적으로 잘 운영되기 때문일 수도 있겠다는 생각도 듦
글을 읽으며 받은 느낌도 그랬음
이 작성자라면 사실 어떤 언어를 쓰더라도 성공적인 엔지니어링 조직을 운영했을 것 같음
함수형 프로그래밍 언어를 쓰면 더 높은 품질의 인력/지원자 풀이 걸러진다는 흔한 생각과도 배치되지는 않음
지금 Real-World OCaml을 읽고 있는데, 이미 몇 가지는 알고 있었지만 함수형 프로그래밍을 더 많이 배우는 중임
함수형 프로그래밍으로 놀랄 만큼 견고한 소프트웨어 조각을 만들 수 있어 보임
하지만 고민도 됨
현재 제품 백엔드는 NiceGUI로 동작하고 있고 역할을 잘 해냄
코드는 합리적이고 MVVM이며, 가장 중요한 일은 고객별로 웹소켓에 연결해 데이터를 소비하고 분석을 보여주는 것임
고객 수는 많지 않을 것이고, 웹사이트 방문자는 수십 명에서 많아야 수백 명 정도일 듯함
REPL이나 핫 리로드도 원하지만, 기능이 늘어나면 사용자 관리 패널, 더 많은 분석 등에서 함수형 프로그래밍이 데이터 파이프라인 변환에 잘 맞을 수도 있다는 건 알고 있음
다만 Haskell이나 OCaml은 정적 언어임
나중에 커지고 확장되면서도 동적인 걸 원한다면 Clojure나 Elixir가 좋은 선택일 것 같음
동시에 언젠가 리팩터링이 필요해지면 망가질까 봐 두려움
현재는 Python과 Mypy를 쓰고, 프런트엔드는 NiceGUI가 백엔드에서 생성함
OCaml은 모르겠지만 Haskell에서는 ghci/cabal repl로 개발 중인 웹앱을 매우 빠르게 다시 로드할 수 있음
솔직히 많은 Haskell 사용자들이 이걸 잘 활용하지 않는다고 봄
Scheme, 나중에는 Racket이라는 비교적 비주류 언어로 비슷한 시스템을 작업한 적이 있는데, 규모는 커졌지만 작은 팀이 오랫동안 관리 가능하고 빠른 속도를 유지했음
버그는 많이 만들지 않았고, 보통 기능을 아주 빠르게 추가할 수 있었음
예를 들어 민감한 데이터를 AWS에 호스팅하기 위한 어떤 인증을 가장 먼저 달성했음
가끔은 인기 플랫폼에서는 기성 구성요소로 해결할 일을 처음부터 만들어야 해서 기능 추가가 느릴 때도 있었음
하지만 일단 만들고 나면 잘 동작했고, 다시 예전 속도로 돌아갔으며 수십 개 기성 프레임워크의 비대함과 복잡성에 느려지지 않았음
관리 가능한 플랫폼을 직접 통제했기 때문에 필요가 생겼을 때 AWS로 빠르게 이동할 수도 있었음
시스템에는 처음부터 복잡한 데이터와 웹 상호작용을 위한 아키텍처 비법도 있었고, 이게 많은 기능을 빠르게 개발하게 해줬으며 이후에도 똑똑한 방향으로 힘을 실어줬음
Haskell 핀테크와 다른 점은 팀 규모가 매우 작았다는 것임
한 번에 소프트웨어 엔지니어는 2~3명뿐이었고 운영을 모두 맡는 사람이 있었음
그래서 수백 명이 조율하면서 일관된 시스템을 유지해야 하는 어려움은 없었음
보통 한 명은 더 기술적이고 아키텍처적인 코드 변경을 맡고, 다른 한 명은 복잡한 프로세스에 대한 방대한 비즈니스 로직 기능을 빠르게 추가했음
현재나 가까운 미래의 LLM류 AI 도구를 신중히 쓰면, 소프트웨어 개발에서도 매우 작고 엄청나게 효과적인 팀의 효율을 일부 얻을 수 있을 것 같음
떠오르는 모델은 스토리 포인트를 없애려고 거대한 비대를 양산하고 지속 가능성은 남의 문제로 미루는 게 아니라, 소수의 아주 날카로운 사고자들이 시스템을 힘을 실어주면서도 관리 가능한 길에 계속 올려두는 방식임
양날의 검임 200만 줄은 대단한 성취지만, 동시에 상당한 유지보수 부담이기도 함
Haskell의 장점은 이론적으로 명확하지만 단점은 직관하기 더 어려움
유혹은 모든 것을 타입으로 모델링하는 데 있음
코드베이스 자체가 애플리케이션이 아니라 비즈니스 명세가 되어버림
정책 변경마다 큰 리팩터링이 되고, Haskell의 안전성 덕에 놀랄 만큼 손이 많이 가는 경우도 있음
결국 둘 다 가질 수는 없고, 언젠가는 타입에 갇히게 됨
Haskell은 특히 이 규모에서 정말 인상적이고 강력하지만 고유한 문제도 가져옴
비즈니스 로직을 타입으로 모델링하려는 유혹은 경직된 구조를 만들고, 그 구조가 주는 안전성은 다른 종류의 위험을 보지 못하게 할 수 있음
취향 있는 경험 많은 엔지니어들이 핵심 부분을 만든다면 그 선을 꽤 잘 탈 수 있음
전부 가질 수는 없지만 많은 건 가질 수 있음
몇 년 전 Jane Street에서 인턴을 했는데, Haskell이 아니라 OCaml이었지만 그 균형을 정말 잘 잡는 것처럼 보였음
내재적 복잡성이 높고 신뢰성과 정확성이 사업 존립과 직결되는 영역인데도 놀랄 만큼 빠르게 움직였음
돌이켜보면 Jane Street의 핵심은 Stephen Weeks처럼 훌륭한 취향을 가진 경험 많은 OCaml 프로그래머를 고용하고, 그들이 처음부터 핵심 라이브러리를 만들고 전체 코드베이스를 이끌게 한 데 있었음
안타깝게도 Mercury는 이 부분을 그만큼 잘하지는 못했음
함수형 프로그래밍에서 내가 겪는 문제는 디버깅임
더 정확히는 명령형 프로그래밍, 특히 절차형 방식의 강점이라고 봄
함수형/선언형 스타일에서는 보통 무언가가 어떻게 만들어지는지가 아니라 어떤 상태여야 하는지를 설명하고, 언어가 모든 것을 조립해 최종 결과를 내게 함
모든 걸 제대로 했다면 좋고 더 나을 수도 있지만, 그렇지 않아서 기대한 결과가 안 나오면 버그를 어떻게 찾을지가 문제임
C 같은 언어에서는 비교적 단순함
한 줄씩 따라가며 각 단계 사이의 실행 상태, 사실상 RAM을 보고 기대와 다르면 그 줄에서 뭔가 잘못된 것이므로 들어가서 그렇게 진행하면 됨
함수형 프로그래밍처럼 언어가 상태를 숨기려고 할수록 이건 더 어려움
글에서 가장 긴 섹션이 이 문제, 즉 “design for introspection”인 것도 흥미로움
작성자는 코드를 디버깅 가능하게 만들기 위해 일부러 많은 노력을 해야 했고, Haskell의 종종 간과되는 실용적 사용에 대한 좋은 통찰을 줌
내 디버깅 요령은 중요도가 조금이라도 있는 모든 코드가 같은 입력에 대해 같은 출력을 반환하게 만드는 것임
사소한 코드도 마찬가지임
다른 주류 언어는 여기에 근접하지 못함
공유 메모리 동시성처럼 그렇게 쓸 수 없는 상황은 트랜잭션을 씀
이것도 다른 주류 언어는 근접하지 못함
여기에 null 없음, 암묵적 정수 캐스팅 없음 같은 쉬운 이점은 넣지도 않았음
Haskell 코드 디버깅이 다른 언어보다 어렵다는 건 완전히 맞음
하지만 하위 90%의 발목잡이를 없애면 당연히 그렇게 될 수밖에 없음
함수형 프로그래밍 디버깅은 명령형 프로그래밍과 달리 REPL 주도인 경우가 많음
물론 함수형에만 고유한 건 아니고, Python이나 JavaScript 같은 대체로 명령형인 언어에서도 Python 셸, 브라우저 콘솔, Node/Deno/Bun 셸, 노트북 등을 첫 디버깅 계층으로 쓰는 일이 많음
REPL 중심 디버깅에는 흥미로운 절충이 있음
C 같은 언어에서는 전체 프로그램 디버깅과 중단점에서 시작해 문제가 있을 것 같은 정확한 지점을 맞히려는 경우가 많음
REPL 중심 세계에서는 프로그램의 구성요소를 REPL에서 직접 더 많이 테스트할 수 있게 만들려 함
그래서 모듈/API/타입 경계가 디버깅 가능성과 닮아감
C/C++ 같은 명령형 언어보다 이런 경계를 제대로, 쓰기 쉽게 만드는 압력이 더 클 때가 있음
반대로 전체 프로그램 우선 디버깅에 비해, 현실의 이상한 시나리오에서 단위 간 복잡한 통합 문제를 분리하기 더 어려워질 때도 있음
하지만 REPL 우선 접근은 통합 표면적을 최소로 줄이도록 유도하는 경우가 많아, 함수형 언어에서는 명령형 언어에서 보이는 통합 효과가 덜 나타나기도 함
함수형 언어가 상태를 숨긴다는 표현은 맞지 않음
이 언어들도 명령형 하드웨어에서 실행되고 실제 하드웨어 상태를 다룸
어느 지점에서는 두 세계 사이의 번역이 있으며, 아마 생각보다 그렇게 다르지도 않음
필요하면 여전히 명령형 중단점과 명령형 디버거로 돌아갈 수 있음
그래서 “REPL 주도” 디버깅이라고 부름
REPL로 문제 있는 단위, 즉 정확한 모듈/API/함수와 놀라운 출력을 내는 입력을 좁힐 수 있음
소스만 보고 버그가 안 보이면 명령형 디버거로 보내 거의 같은 한 줄씩 실행 경험을 볼 수 있고, 추가 맥락을 얻을 수도 있음
그 시점에는 이미 REPL로 충분히 좁혀서 단위 자체가 작고 좁기 때문에 좋은 중단점을 고를 필요도 별로 없을 가능성이 큼
글의 “design for introspection” 섹션에서 받은 메시지는 잘못 잡은 것 같음
그 섹션은 디버깅 가능성이 아니라 관측 가능성에 대한 것임
로깅/텔레메트리 시스템을 올바르게 연결하고, 테스트 중 가짜를 모킹하고, 개별 라이브러리에 맡기는 대신 재시도/회로 차단기를 시스템 전체 수준에서 추가하는 얘기였음
명령형 세계에서도 이건 디버깅 문제가 아니라 의존성 주입, 미들웨어 설치, 공개 API 경계에서 구체 클래스보다 추상 인터페이스를 쓰는 식의 분해 문제임
이런 설계 제안은 리팩터링이고, 디버깅 가능성보다는 남의 공개 API에 관측 가능성 미들웨어를 얼마나 쉽게 설치할 수 있는지에 영향을 줌
Haskell 200만 줄이 대체 뭘 하고 있을지 상상하기 어려움
코드가 정말 많은데, Haskell은 적은 코드로 많은 일을 할 수 있는 “촘촘한” 언어라는 인상이 있음
JSON 직렬화/역직렬화, REST API 프레임워크, 로깅 같은 걸 위한 라이브러리가 많아서 그런 걸까 싶음
원문에 따르면, 계측할 수 없는 코드는 신뢰할 수 없다는 게 문제임
서드파티 바인딩이 구체 함수로 HTTP 호출을 하면 추적을 추가할 방법도 없고, SLO에 맞춘 타임아웃을 주입할 방법도 없고, 테스트에서 파트너 장애를 시뮬레이션할 방법도 없으며, 추적의 400ms 공백을 이론으로 때려맞히는 것 말고 설명할 방법도 없음
그래서 직접 작성함
초기에 일이 더 많지만, 직접 만든 클라이언트는 처음부터 그렇게 만들었기 때문에 관측 가능하도록 구성됨
“촘촘하다”고 부른 성질은 보통 표현력이 높다고 함
상대적으로 매우 추상적인 생각을 적은 문자로 표현할 수 있다는 뜻임
어떤 사람들은 이것을 “고수준”이라고도 부름
다만 200만 줄은 처음 들리는 것만큼 많은 코드는 아니라고 봄
특히 금융처럼 규제가 많은 영역의 회사이고 몇 년간 쌓인 코드라면 더 그렇음
객관적 지표는 전혀 아니지만, Haskell은 그냥 종횡비가 다르다고 느꼈음
줄 수는 어느 정도 적을 수 있지만, 단어 수는 더 명령형인 객체지향 언어들과 대체로 비슷함
코드베이스가 실제로 어떤지는 모르지만, Haskell이 간결하다는 평판은 일부는 학계나 범주론 쪽에서 과대표집되기 때문임
그쪽에서는 St M -> C T 같은 표현도 괜찮지만, 실제 소프트웨어에서는 TransactionState Debit -> Verified Transaction처럼 쓰는 게 훨씬 유용함
또 다른 부분은 LISP까지 거슬러 올라가는 문화적 요인임
사람들이 이해하기 어려운 요령이나 매크로로 줄 수를 아끼려고 지나치게 영리하게 구는 경향이 있음
Mercury 같은 금융 회사에서는 이런 방식보다 명확성과 가독성이 장려될 것 같음
예컨대 린터가 >>와 >>=로 한 줄에 쓰는 대신, 모나드 코드를 꼼꼼한 여러 줄짜리 do 표현식으로 나누게 할 수도 있음
Hacker News 의견들
Haskell이 이런 걸 타입으로 강제하는 데 가장 강력한 축에 드는 언어인 건 맞지만, 같은 패턴은 Rust와 TypeScript에서도 꽤 잘 먹힘
User -> LoggedInUser -> AccessControlledLoggedInUser 같은 흐름으로 웹앱에서 반복되는 명백한 권한 부여 버그를 막는 방식도 좋아함
업계에서는 이 패턴이 엄청나게 덜 쓰이고 있다고 봄
보안상 이스케이프 전/후 문자열을 구분해야 한다면 동적 타입 언어에서도 Escaped 클래스로 감싸고
escape(str)->Escaped,dangerouslyAssumeEscaped(str)->Escaped같은 함수를 둘 수 있음성능 비용이 있으니 절충은 필요하지만 가능함
또 다른 방식은 Application Hungarian이고, 다만 이건 컴파일러보다 프로그래머의 규율에 더 의존함: https://www.joelonsoftware.com/2005/05/11/making-wrong-code-...
예를 들어 C#에서도 충분히 할 수 있지만, 실제 타입 정의보다 시각적 잡음이 더 커지는 식임
다만 “모나드는 무서우니 튜토리얼을 써야겠다” 같은 효과를 피하려고 그걸 굳이 말하지 않고 이름도 다르게 부르는 편임
모나드보다는 타입 클래스 같은 쪽 영향이 더 큼
명목 타입이 없어서 원시 타입을 감싸는 newtype 같은 걸 만들려면 꽤 해키한 주문을 기억해야 함
내 경험상 이런 타입 안전성을 강제하는 데는 OCaml이 Rust보다 더 강력했음
GADT로 표현력이 더 크고, 다형 변이와 객체 타입/레코드 행 타입으로 편의성도 있으며, 모듈 시스템과 펑터도 있음
가비지 컬렉션이면 충분한 영역에서는 Rust의 빌림 검사기 때문에 생기는 추상화 제약과 어려움도 피할 수 있음
몇 년간 Haskell로 일하는 걸 정말 좋아했음
일부러 찾던 건 아니었지만 기회가 우연히 왔고, 흥미롭고 지적으로 자극적이었음
다만 안타깝게도 Haskell만 3년을 쓴 뒤에도 Rust에서의 생산성이 Haskell의 쉽게 두 배는 됨
Haskell에는 미리 알고 피해야 하는 함정이 더 많고, 작성자에 따라 거의 읽기 전용 언어처럼 소화하기 어려울 때가 있음
도구 체인은 종종 Nix와 결합되어 있는데 Nix 자체도 복잡한 괴물이고, 언어 확장은 사방에 퍼져 있는 느낌임
Cabal 파일도 별로고, 컴파일러 오류에 익숙해지는 데 시간이 걸림
마지막 제품에서 백엔드를 Typescript에서 Rust로 옮기기 시작했는데, 크래시에 지쳐서였음
지금은 그걸 내가 저지른 가장 큰 기술적 실수 중 하나로 봄. 생산성이 엄청나게 느려졌기 때문임
Rust에서만 생긴 시간 낭비 예로는, 데이터베이스 연결을 열고 뭔가 한 뒤 닫는 식의 고차 함수 작성이 Haskell, TypeScript, JavaScript, C++, PHP에서는 사소한데 Rust에서는 Rust 전문가 친구들에게 물어봐도 사실상 불가능해서 포기하게 된 일이 있음
또 리팩터링을 시도해 하루 종일 타입 오류를 고치다가 최상위 파일에서 오류를 만나고, 알고 보니 설계의 기본 부분 때문에 전체 리팩터링이 불가능하다고 판단해 전부 되돌린 적이 여러 번 있음
게다가 Rust는 구체 타입 대신 인터페이스로 값 사용하는 일이 상황에 따라 고급 기법과 불가능 사이 어딘가에 있는, 내가 떠올릴 수 있는 유일한 현대 언어임
그래서 애플리케이션 코드, 즉 시스템 코드나 라이브러리 코드가 아닌 코드는 대략 Rust로 쓰면 안 된다는 결론에 도달함
그리고 “읽기 전용”이라는 건 무슨 뜻인지도 궁금함
일반적인 인식과는 다르게, Mercury가 Haskell을 선택했고 초기 리더들이 Haskell에서 풍부한 경험을 가졌다는 점이 성공에 적지 않은 역할을 했을 수 있다고 봄
Mercury 고객 입장에서 이 회사는 내 도구함의 핵심 회사 중 하나이고, Haskell 선택이 그들의 진행, 개발, 전체 여정을 더 좋게 만들었다는 느낌을 지울 수 없음
물론 대부분의 언어에 대해 이런 주장을 할 수 있고, Haskell 같은 함수형 언어가 성공 공식이라는 뜻은 아님
하지만 “vibe coding”과 LLM 시대 이전에 이런 의도적 결정을 한 건 특히 선견지명이 있어 보이고, 글에서 자세히 다룬 엔지니어링 문화와 결합된 결과라고 봄
나도 좋은 기술 문화를 좋아하지만, 훌륭한 기술 문화를 가진 회사가 나쁜 사업 초점 때문에 죽는 걸 봤음
더 나아가 스타트업식 핀테크 문화가 좋은 기술 문화를 낳았을 수도 있음
은행으로 출발하지 않았기 때문에, 예컨대 SVB와 달리 그렇게 보수적일 필요도 없었고 끔찍한 고대 기술 스택과 통합할 필요도 없었음
Haskell로 성공한 건 기쁘지만 Jane Street와 OCaml처럼, 회사가 믿게 만들고 싶어 하는 것과 달리 언어 선택은 사업 측면에서는 거의 우연에 가깝다고 봄
다만 프런트엔드는 뭘 쓰는지 궁금함. 아마 이 Haskell은 전부 백엔드일 것 같음
새로 온 사람들에게 문화와 스타일을 처음부터 심을 수 있었기 때문임
vibe coding 이전이라면 그런 사람들 대부분은 아무 지시 없이 그냥 뛰어들어 해킹하려고 하지는 않았을 것임
다른 서비스에서 넘어오면 정말 만족스러움
절친이 이 회사에서 일하는데, 밖에서 보기에도 엔지니어링 문화가 좋아 보임
Haskell은 이 일에 맞는 도구이고 강점을 잘 살리고 있다고 보지만, 성공의 상당 부분은 그냥 회사가 전반적으로 잘 운영되기 때문일 수도 있겠다는 생각도 듦
이 작성자라면 사실 어떤 언어를 쓰더라도 성공적인 엔지니어링 조직을 운영했을 것 같음
지금 Real-World OCaml을 읽고 있는데, 이미 몇 가지는 알고 있었지만 함수형 프로그래밍을 더 많이 배우는 중임
함수형 프로그래밍으로 놀랄 만큼 견고한 소프트웨어 조각을 만들 수 있어 보임
하지만 고민도 됨
현재 제품 백엔드는 NiceGUI로 동작하고 있고 역할을 잘 해냄
코드는 합리적이고 MVVM이며, 가장 중요한 일은 고객별로 웹소켓에 연결해 데이터를 소비하고 분석을 보여주는 것임
고객 수는 많지 않을 것이고, 웹사이트 방문자는 수십 명에서 많아야 수백 명 정도일 듯함
REPL이나 핫 리로드도 원하지만, 기능이 늘어나면 사용자 관리 패널, 더 많은 분석 등에서 함수형 프로그래밍이 데이터 파이프라인 변환에 잘 맞을 수도 있다는 건 알고 있음
다만 Haskell이나 OCaml은 정적 언어임
나중에 커지고 확장되면서도 동적인 걸 원한다면 Clojure나 Elixir가 좋은 선택일 것 같음
동시에 언젠가 리팩터링이 필요해지면 망가질까 봐 두려움
현재는 Python과 Mypy를 쓰고, 프런트엔드는 NiceGUI가 백엔드에서 생성함
cabal repl로 개발 중인 웹앱을 매우 빠르게 다시 로드할 수 있음솔직히 많은 Haskell 사용자들이 이걸 잘 활용하지 않는다고 봄
Scheme, 나중에는 Racket이라는 비교적 비주류 언어로 비슷한 시스템을 작업한 적이 있는데, 규모는 커졌지만 작은 팀이 오랫동안 관리 가능하고 빠른 속도를 유지했음
버그는 많이 만들지 않았고, 보통 기능을 아주 빠르게 추가할 수 있었음
예를 들어 민감한 데이터를 AWS에 호스팅하기 위한 어떤 인증을 가장 먼저 달성했음
가끔은 인기 플랫폼에서는 기성 구성요소로 해결할 일을 처음부터 만들어야 해서 기능 추가가 느릴 때도 있었음
하지만 일단 만들고 나면 잘 동작했고, 다시 예전 속도로 돌아갔으며 수십 개 기성 프레임워크의 비대함과 복잡성에 느려지지 않았음
관리 가능한 플랫폼을 직접 통제했기 때문에 필요가 생겼을 때 AWS로 빠르게 이동할 수도 있었음
시스템에는 처음부터 복잡한 데이터와 웹 상호작용을 위한 아키텍처 비법도 있었고, 이게 많은 기능을 빠르게 개발하게 해줬으며 이후에도 똑똑한 방향으로 힘을 실어줬음
Haskell 핀테크와 다른 점은 팀 규모가 매우 작았다는 것임
한 번에 소프트웨어 엔지니어는 2~3명뿐이었고 운영을 모두 맡는 사람이 있었음
그래서 수백 명이 조율하면서 일관된 시스템을 유지해야 하는 어려움은 없었음
보통 한 명은 더 기술적이고 아키텍처적인 코드 변경을 맡고, 다른 한 명은 복잡한 프로세스에 대한 방대한 비즈니스 로직 기능을 빠르게 추가했음
현재나 가까운 미래의 LLM류 AI 도구를 신중히 쓰면, 소프트웨어 개발에서도 매우 작고 엄청나게 효과적인 팀의 효율을 일부 얻을 수 있을 것 같음
떠오르는 모델은 스토리 포인트를 없애려고 거대한 비대를 양산하고 지속 가능성은 남의 문제로 미루는 게 아니라, 소수의 아주 날카로운 사고자들이 시스템을 힘을 실어주면서도 관리 가능한 길에 계속 올려두는 방식임
양날의 검임
200만 줄은 대단한 성취지만, 동시에 상당한 유지보수 부담이기도 함
Haskell의 장점은 이론적으로 명확하지만 단점은 직관하기 더 어려움
유혹은 모든 것을 타입으로 모델링하는 데 있음
코드베이스 자체가 애플리케이션이 아니라 비즈니스 명세가 되어버림
정책 변경마다 큰 리팩터링이 되고, Haskell의 안전성 덕에 놀랄 만큼 손이 많이 가는 경우도 있음
결국 둘 다 가질 수는 없고, 언젠가는 타입에 갇히게 됨
Haskell은 특히 이 규모에서 정말 인상적이고 강력하지만 고유한 문제도 가져옴
비즈니스 로직을 타입으로 모델링하려는 유혹은 경직된 구조를 만들고, 그 구조가 주는 안전성은 다른 종류의 위험을 보지 못하게 할 수 있음
전부 가질 수는 없지만 많은 건 가질 수 있음
몇 년 전 Jane Street에서 인턴을 했는데, Haskell이 아니라 OCaml이었지만 그 균형을 정말 잘 잡는 것처럼 보였음
내재적 복잡성이 높고 신뢰성과 정확성이 사업 존립과 직결되는 영역인데도 놀랄 만큼 빠르게 움직였음
돌이켜보면 Jane Street의 핵심은 Stephen Weeks처럼 훌륭한 취향을 가진 경험 많은 OCaml 프로그래머를 고용하고, 그들이 처음부터 핵심 라이브러리를 만들고 전체 코드베이스를 이끌게 한 데 있었음
안타깝게도 Mercury는 이 부분을 그만큼 잘하지는 못했음
솔직히 튜링 완전한 타입 시스템의 가장 큰 단점은 이론상 컴파일하면 먼지가 되는 애플리케이션을 구현할 수 있다는 것임
Bellroy의 비슷한 Haskell 성공 사례가 곧 열리는 Melbourne Compose 모임 주제임: https://luma.com/uhdgct1v
함수형 프로그래밍에서 내가 겪는 문제는 디버깅임
더 정확히는 명령형 프로그래밍, 특히 절차형 방식의 강점이라고 봄
함수형/선언형 스타일에서는 보통 무언가가 어떻게 만들어지는지가 아니라 어떤 상태여야 하는지를 설명하고, 언어가 모든 것을 조립해 최종 결과를 내게 함
모든 걸 제대로 했다면 좋고 더 나을 수도 있지만, 그렇지 않아서 기대한 결과가 안 나오면 버그를 어떻게 찾을지가 문제임
C 같은 언어에서는 비교적 단순함
한 줄씩 따라가며 각 단계 사이의 실행 상태, 사실상 RAM을 보고 기대와 다르면 그 줄에서 뭔가 잘못된 것이므로 들어가서 그렇게 진행하면 됨
함수형 프로그래밍처럼 언어가 상태를 숨기려고 할수록 이건 더 어려움
글에서 가장 긴 섹션이 이 문제, 즉 “design for introspection”인 것도 흥미로움
작성자는 코드를 디버깅 가능하게 만들기 위해 일부러 많은 노력을 해야 했고, Haskell의 종종 간과되는 실용적 사용에 대한 좋은 통찰을 줌
사소한 코드도 마찬가지임
다른 주류 언어는 여기에 근접하지 못함
공유 메모리 동시성처럼 그렇게 쓸 수 없는 상황은 트랜잭션을 씀
이것도 다른 주류 언어는 근접하지 못함
여기에 null 없음, 암묵적 정수 캐스팅 없음 같은 쉬운 이점은 넣지도 않았음
Haskell 코드 디버깅이 다른 언어보다 어렵다는 건 완전히 맞음
하지만 하위 90%의 발목잡이를 없애면 당연히 그렇게 될 수밖에 없음
물론 함수형에만 고유한 건 아니고, Python이나 JavaScript 같은 대체로 명령형인 언어에서도 Python 셸, 브라우저 콘솔, Node/Deno/Bun 셸, 노트북 등을 첫 디버깅 계층으로 쓰는 일이 많음
REPL 중심 디버깅에는 흥미로운 절충이 있음
C 같은 언어에서는 전체 프로그램 디버깅과 중단점에서 시작해 문제가 있을 것 같은 정확한 지점을 맞히려는 경우가 많음
REPL 중심 세계에서는 프로그램의 구성요소를 REPL에서 직접 더 많이 테스트할 수 있게 만들려 함
그래서 모듈/API/타입 경계가 디버깅 가능성과 닮아감
C/C++ 같은 명령형 언어보다 이런 경계를 제대로, 쓰기 쉽게 만드는 압력이 더 클 때가 있음
반대로 전체 프로그램 우선 디버깅에 비해, 현실의 이상한 시나리오에서 단위 간 복잡한 통합 문제를 분리하기 더 어려워질 때도 있음
하지만 REPL 우선 접근은 통합 표면적을 최소로 줄이도록 유도하는 경우가 많아, 함수형 언어에서는 명령형 언어에서 보이는 통합 효과가 덜 나타나기도 함
함수형 언어가 상태를 숨긴다는 표현은 맞지 않음
이 언어들도 명령형 하드웨어에서 실행되고 실제 하드웨어 상태를 다룸
어느 지점에서는 두 세계 사이의 번역이 있으며, 아마 생각보다 그렇게 다르지도 않음
필요하면 여전히 명령형 중단점과 명령형 디버거로 돌아갈 수 있음
그래서 “REPL 주도” 디버깅이라고 부름
REPL로 문제 있는 단위, 즉 정확한 모듈/API/함수와 놀라운 출력을 내는 입력을 좁힐 수 있음
소스만 보고 버그가 안 보이면 명령형 디버거로 보내 거의 같은 한 줄씩 실행 경험을 볼 수 있고, 추가 맥락을 얻을 수도 있음
그 시점에는 이미 REPL로 충분히 좁혀서 단위 자체가 작고 좁기 때문에 좋은 중단점을 고를 필요도 별로 없을 가능성이 큼
글의 “design for introspection” 섹션에서 받은 메시지는 잘못 잡은 것 같음
그 섹션은 디버깅 가능성이 아니라 관측 가능성에 대한 것임
로깅/텔레메트리 시스템을 올바르게 연결하고, 테스트 중 가짜를 모킹하고, 개별 라이브러리에 맡기는 대신 재시도/회로 차단기를 시스템 전체 수준에서 추가하는 얘기였음
명령형 세계에서도 이건 디버깅 문제가 아니라 의존성 주입, 미들웨어 설치, 공개 API 경계에서 구체 클래스보다 추상 인터페이스를 쓰는 식의 분해 문제임
이런 설계 제안은 리팩터링이고, 디버깅 가능성보다는 남의 공개 API에 관측 가능성 미들웨어를 얼마나 쉽게 설치할 수 있는지에 영향을 줌
Haskell 200만 줄이 대체 뭘 하고 있을지 상상하기 어려움
코드가 정말 많은데, Haskell은 적은 코드로 많은 일을 할 수 있는 “촘촘한” 언어라는 인상이 있음
JSON 직렬화/역직렬화, REST API 프레임워크, 로깅 같은 걸 위한 라이브러리가 많아서 그런 걸까 싶음
서드파티 바인딩이 구체 함수로 HTTP 호출을 하면 추적을 추가할 방법도 없고, SLO에 맞춘 타임아웃을 주입할 방법도 없고, 테스트에서 파트너 장애를 시뮬레이션할 방법도 없으며, 추적의 400ms 공백을 이론으로 때려맞히는 것 말고 설명할 방법도 없음
그래서 직접 작성함
초기에 일이 더 많지만, 직접 만든 클라이언트는 처음부터 그렇게 만들었기 때문에 관측 가능하도록 구성됨
상대적으로 매우 추상적인 생각을 적은 문자로 표현할 수 있다는 뜻임
어떤 사람들은 이것을 “고수준”이라고도 부름
다만 200만 줄은 처음 들리는 것만큼 많은 코드는 아니라고 봄
특히 금융처럼 규제가 많은 영역의 회사이고 몇 년간 쌓인 코드라면 더 그렇음
줄 수는 어느 정도 적을 수 있지만, 단어 수는 더 명령형인 객체지향 언어들과 대체로 비슷함
그쪽에서는
St M -> C T같은 표현도 괜찮지만, 실제 소프트웨어에서는TransactionState Debit -> Verified Transaction처럼 쓰는 게 훨씬 유용함또 다른 부분은 LISP까지 거슬러 올라가는 문화적 요인임
사람들이 이해하기 어려운 요령이나 매크로로 줄 수를 아끼려고 지나치게 영리하게 구는 경향이 있음
Mercury 같은 금융 회사에서는 이런 방식보다 명확성과 가독성이 장려될 것 같음
예컨대 린터가
>>와>>=로 한 줄에 쓰는 대신, 모나드 코드를 꼼꼼한 여러 줄짜리do표현식으로 나누게 할 수도 있음