수백만 줄의 Haskell: Mercury의 프로덕션 엔지니어링

6 hours ago 2
  • Mercury는 주석 등을 제외한 약 200만 줄의 Haskell 코드베이스로 30만 개 이상 기업에 뱅킹 서비스를 제공하며, 2025년에 2,480억 달러 거래량과 연환산 매출 6억 5,000만 달러를 처리함
  • Mercury의 Haskell 활용 가치는 순수성 자체보다 운영 지식을 API와 타입에 담고, 위험한 동작을 좁은 경계 뒤에 두며, 안전한 경로를 쉬운 경로로 만드는 데 있음
  • 신뢰성은 실패를 모두 막는 것이 아니라 시스템이 변동을 흡수하는 능력으로 다뤄지며, 타입 시스템은 오류 클래스를 배제하고 제도적 지식을 컴파일러가 강제하는 문서처럼 남겨줌
  • Mercury는 금융 업무 흐름의 재시도, 타임아웃, 취소, 크래시 복구를 위해 Temporaldurable execution 프레임워크로 쓰고, Haskell SDK hs-temporal-sdk를 오픈소스로 공개함
  • Haskell의 프로덕션 가치는 모든 것을 타입에 넣는 데 있지 않고, 데이터 손실·금융 오류·규제 문제로 이어지는 불변식은 타입으로 보호하되 복잡성은 캡슐화하고 테스트·문서·코드 리뷰와 함께 운영하는 데 있음

Mercury의 Haskell 운영 규모와 신뢰성 관점

  • Mercury는 주석 등을 제외하고 약 200만 줄 규모의 Haskell 코드베이스를 운영함
  • Mercury는 30만 개 이상의 기업에 뱅킹 서비스를 제공하는 핀테크 회사이며, 2025년에 2,480억 달러 거래량과 연환산 매출 6억 5,000만 달러를 처리함
  • 직원은 약 1,500명이고, 엔지니어링 조직은 주로 범용 개발자를 채용하며 대부분은 입사 전 Haskell을 써본 적이 없음
  • 이 시스템은 빠른 성장, SVB 위기로 5일 만에 20억 달러 신규 예금이 유입된 상황, 규제 심사, 대규모 금융 시스템의 일반적·비일반적 상황을 거치며 수년간 동작해 옴

신뢰성은 실패 방지가 아니라 변동 흡수 능력

  • 전통적 신뢰성 접근은 실패를 열거하고, 검사와 테스트를 추가하고, 버그를 찾는 데 집중하지만 이것만으로는 충분하지 않음
  • Mercury는 신뢰성을 시스템이 변동을 흡수하는 능력으로 다룸
    • 시스템이 우아하게 성능 저하를 겪을 수 있어야 함
    • 운영자가 시스템을 이해하고 조정할 수 있어야 함
    • 아키텍처가 올바른 일을 쉽게, 잘못된 일을 어렵게 만들어야 함
  • 빠르게 성장하는 조직에서는 새로 합류한 엔지니어가 모듈을 읽고 이해할 수 있는지, 데이터베이스가 느릴 때 서비스가 함께 무너지는지, 인터페이스 오용을 컴파일러가 잡는지가 실제 운영 질문이 됨
  • 타입 시스템은 단순한 정확성 증명보다 운영 보조 장치에 가까움
    • 특정 오류 클래스를 배제함
    • 작성자가 떠난 뒤에도 제도적 지식을 컴파일러가 읽을 수 있는 형태로 남김
    • 위키보다 일관되게 강제되는 문서 역할을 함
  • Mercury의 안정성 엔지니어링은 제품 개발을 늦추는 품질 경찰이 아니라, 기능이 깨졌을 때의 영향을 설계 초기부터 다루는 협업 방식임
    • 실패 시 폭발 반경
    • 멱등성이 필요한 작업과 방식
    • 롤백 형태
    • 진행 중 작업의 처리
    • 실패를 흡수하는 시스템과 증폭하는 시스템을 미리 따짐

순수성은 언어의 속성이 아니라 인터페이스 경계

  • Haskell의 순수성은 내부에 부작용이 전혀 없다는 뜻이 아니라, 인터페이스가 부작용의 누출을 막는 경계를 만든다는 의미에 가까움
  • bytestring, text, vector 같은 라이브러리의 순수 함수 뒤에는 가변 할당, 버퍼 쓰기, unsafe coercion 같은 내부 구현이 존재함
  • ST 모나드는 계산 안에서 관찰 가능한 제자리 변경과 부작용을 사용하지만, runST의 rank-2 타입이 내부에서 만든 가변 참조의 탈출을 막음 runST :: (forall s. ST s a) -> a
  • 내부에서는 명령형 동작이 가능하지만, 외부에는 결과만 나오며 변경 가능 상태는 경계 밖으로 새지 않음
  • 이 원칙은 운영 시스템 전반에 적용됨
    • 데이터베이스 계층은 내부적으로 연결 풀링, 재시도, 가변 상태를 사용할 수 있음
    • 캐시는 동시성 가변 맵을 사용할 수 있음
    • HTTP 클라이언트는 회로 차단기, 연결 풀, 많은 장부 관리를 가질 수 있음
    • 핵심은 위험한 동작을 좁은 인터페이스로 감싸 오용을 어렵게 만드는 것임
  • 실제 시스템에서 목표는 변경을 완전히 피하는 것이 아니라, 변경이 어디에 있는지 명확히 하고 코드베이스 중 누가 그것을 알아야 하는지 제한하는 것임

올바른 일을 쉬운 일로 만들기

  • 대형 코드베이스에서는 정확성이 특정 순서나 보이지 않는 추가 단계에 의존하는 패턴이 자주 생김
    • 트랜잭션 후 감사 로그를 flush해야 함
    • 엔드포인트 호출 전 feature flag를 확인해야 함
    • 알림 enqueue를 데이터베이스 트랜잭션 안에서 해야 함
  • 이런 운영 지식이 위키, 온보딩 문서, 과거 디자인 리뷰, Slack 스레드, 일부 시니어 엔지니어의 기억에만 있으면 빠르게 사라짐
  • Haskell은 이런 절차를 타입으로 인코딩해 잊을 수 없게 만들 수 있음
  • 나쁜 방식은 올바른 함수를 쓰라고 부탁하되 우회 경로를 남겨두는 것임 -- Please use this one, not the other one writeWithEvents :: Transaction -> [Event] -> IO () -- Don't use this directly (but we can't stop you) writeTransaction :: Transaction -> IO () publishEvents :: [Event] -> IO ()
    • 더 나은 방식은 작업을 실행하는 유일한 경로가 이벤트 발행을 포함하도록 타입을 재구성하는 것임
    data Transact a -- opaque; cannot be run directly record :: Transaction -> Transact () emit :: Event -> Transact () -- The *only* way to execute a Transact: commit and publish atomically commit :: Transact a -> IO a
  • 여기서 타입 시스템은 이벤트에 관한 깊은 정리를 증명하기보다, 올바른 운영 절차를 가장 쉬운 경로로 만듦
  • 새 엔지니어가 트랜잭션 작성법을 물으면 타입 시그니처와 공개 API가 답을 제공하고, 시니어 엔지니어가 떠나도 지식이 남음

지속 실행과 Temporal

  • 금융 시스템의 업무 흐름은 단일 트랜잭션 안에 머물지 않음
    • 결제 전송
    • 파트너 승인 대기
    • 원장 업데이트
    • 고객 알림
    • 취소와 타임아웃 처리
    • 파트너는 성공했지만 워커가 기록 전 죽은 경우
    • 네트워크 문제로 응답이 없는 경우
  • 이런 흐름에는 상태, 재시도, 타임아웃, 멱등성, 프로세스 크래시와 배포를 넘어 지속되는 실행이 필요함
  • Mercury는 과거에 데이터베이스 기반 상태 머신, cron 작업, 백그라운드 워커, 코드 곳곳의 재시도와 타임아웃 처리로 이런 프로세스를 조정함
    • 동작은 했지만 취약했고 이해하기 어려웠으며 운영 사고의 불균형한 원인이 됨
  • Temporal은 Mercury의 durable execution 프레임워크로, 워크플로를 일반적인 순차 코드처럼 작성하고 플랫폼이 각 단계를 이벤트 히스토리에 기록함
  • 워커가 워크플로 중간에 크래시하면 다른 워커가 결정적 prefix를 replay해 상태를 재구성하고 중단 지점부터 계속함
  • 재시도, 타임아웃, 취소, 오류 처리는 각 팀이 따로 재구현하는 대신 플랫폼이 제공함
  • Temporal workflow는 이벤트 히스토리에 대한 순수 함수와 비슷한 성격을 가짐
    • replay된 workflow는 원래와 같은 명령 시퀀스를 만들어야 함
    • 이 결정성 요구는 순수 코드의 같은 입력·같은 출력 제약과 닮아 있음
    • 부작용은 workflow의 IO에 해당하는 activity로 격리됨
  • Mercury는 Temporal의 공식 Core SDK를 Rust FFI로 감싼 Haskell SDK hs-temporal-sdk를 만들고 오픈소스로 공개함
  • Temporal 도입 패턴은 Temporal Replay conference 발표에서도 다뤄졌고, Mercury는 취약한 cron·상태 머신 체인을 durable workflow로 대체해 운영 개선을 얻음

도메인은 전송 계층이 아니라 비즈니스 언어로 설계

  • 성장한 시스템에서 흔한 실수는 호출 시스템의 개념이 도메인 모델에 새는 것임
  • HTTP 요청 핸들러용으로 작성된 코드가 나중에 cron 작업, 큐 기반 백그라운드 워커, Temporal workflow에서 재사용되면 StatusCodeException 409 "Conflict" 같은 HTTP 예외가 비HTTP 맥락으로 전파될 수 있음
  • cron 작업에는 409 응답을 기다리는 호출자가 없으며, 상태 코드는 비즈니스 의미를 잘못된 계층으로 끌고 감
  • 해결책은 도메인 오류를 도메인 타입으로 모델링하는 것임
    • 잔액 부족은 InsufficientFunds여야 함
    • 중복 요청은 DuplicateRequest여야 함
    • 파트너 타임아웃은 PartnerTimeout이어야 함
  • 각 경계에는 얇은 변환 계층을 둠 data PaymentError = InsufficientFunds | DuplicateRequest RequestId | PartnerTimeout Partner toHttpError :: PaymentError -> HttpResponse toHttpError InsufficientFunds = err402 "Insufficient funds" toHttpError (DuplicateRequest _) = err409 "Duplicate request" toHttpError (PartnerTimeout _) = err502 "Partner unavailable" toWorkerStrategy :: PaymentError -> WorkerAction toWorkerStrategy InsufficientFunds = Fail "Insufficient funds" toWorkerStrategy (DuplicateRequest _) = Skip toWorkerStrategy (PartnerTimeout _) = RetryWithBackoff
  • 전송 계층 관심사는 가장자리에 있어야 하며, 도메인 모델은 웹 핸들러, CLI, cron 작업, 백그라운드 워커, workflow 엔진 어디서 호출돼도 HTTP 상태 코드를 끌고 다니지 않아야 함

타입 인코딩의 비용과 적정선

  • 불변식을 타입에 넣는 것은 강력하지만 인지 비용, 경직성, 요구사항 변경 시 어려움을 만든다는 비용이 있음
  • 위반이 데이터 손실, 금융 오류, 규제 문제, 호출 대기 사고로 이어진다면 타입 인코딩 비용이 정당화됨
  • 단지 현재 방식이 그렇다거나, 타입 수준 기법을 적용해 보고 싶다는 이유라면 코드베이스를 바꾸기 어렵게 만들 가능성이 큼
  • 너무 많이 인코딩하는 쪽

    • 불법 상태가 표현 불가능하고 도메인이 타입으로 충실히 모델링됨
    • 비즈니스 규칙 변경이 50개 모듈을 관통하는 타입 변경으로 이어져 리팩터링이 길어짐
    • 새 엔지니어가 타입 시그니처를 이해하기 어려워짐
  • 아무것도 인코딩하지 않는 쪽

    • 타입이 String, IO (), 최악의 경우 Dynamic에 가까워짐
    • 코드는 바꾸기 쉽지만 계약이 없고, 의미는 기존 작성자의 기억에 의존함
    • 작성자가 떠나면 시스템이 왜 동작하지 않는지 알기 어려워짐
  • 유용한 기준

    • 조용한 손상을 막는 불변식은 타입에 넣는 편이 좋음
      • 이벤트 없이 커밋된 트랜잭션
      • 감사 로그 없이 처리된 결제
      • 겉보기엔 가능하지만 의미적으로 불가능한 상태 전이
    • 크게 실패하는 불변식은 좋은 오류 메시지를 가진 런타임 검사로 충분할 수 있음
      • 500 응답
      • assertion 실패
      • JSON 경계의 타입 불일치
    • 전체 도메인을 타입으로 모델링하려는 욕구는 억제해야 함
      • 도메인에는 예외, 과거 호환 규칙, 서로 충돌하는 규칙, 특정 고객용 특수 동작이 존재함
    • 타입은 컴파일러만이 아니라 팀을 위한 도구임
      • 테스트, 문서, 코드 리뷰, 예제, 플레이북과 함께 방어층을 구성해야 함
    • Mercury 내부에는 GADT, type family, 상태 전이를 추적하는 phantom type 같은 복잡한 타입 수준 장치를 쓰는 라이브러리도 있음
    • 잘못되면 돈이 잘못 이동하거나 규제 불변식이 깨지는 메커니즘에서는 이런 복잡성이 필요함
    • 핵심은 복잡성을 캡슐화하는 것임
    • 타입 수준 상태 머신을 구현하는 모듈은 소수의 깊이 이해한 작성자와 충분한 테스트를 가져야 함
    • 사용하는 쪽의 API는 일반적인 타입을 가진 몇 개 함수처럼 보여야 함
    • product engineer가 내부의 타입 수준 증명 장치를 모르고도 안전하게 호출할 수 있어야 함
    • 코드 리뷰에서 다른 모듈을 만지는 PR이 컴파일러를 달래기 위해 복사한 타입 주석으로 가득하다면 추상화가 경계를 넘어 새고 있다는 신호임

내성 가능성을 위한 설계

  • 신뢰성이 적응 능력이라면 내성 가능성은 그 능력을 얻는 방식 중 하나임
  • 운영자는 볼 수 없는 것을 운영할 수 없고, 팀은 내부가 불투명한 시스템에 적응하기 어려움
  • Haskell에는 monkey patching이 없어 런타임에 라이브러리 내부 HTTP 클라이언트를 바꾸거나 데이터베이스 호출을 OpenTelemetry span을 내는 함수로 교체하기 어려움
  • Rust도 같은 제약을 갖지만 Rust 생태계는 tower 미들웨어 패턴에 수렴한 반면, Haskell 생태계는 여러 접근으로 나뉘어 있음
  • 라이브러리가 구체적인 최상위 함수 묶음만 노출하면, 계측하려면 새 모듈로 감싸고 사람들이 원래 모듈 대신 그 모듈을 import하길 기대해야 함
  • 함수 레코드

    • 가장 자주 쓰는 해법은 구체 함수 대신 함수 레코드를 노출하는 것임 -- A concrete module gives you no leverage: sendRequest :: Request -> IO Response -- A record of functions gives you all of it: data HttpClient = HttpClient { sendRequest :: Request -> IO Response , getManager :: IO Manager }
    • 이 방식이면 sendRequest를 타이밍 계측으로 감싸 새 HttpClient를 반환할 수 있음
    • 테스트용 fault injection, mock 교체, 재시도, tracing, 요청 rewrite, tenant별 동작 같은 횡단 관심사를 런타임에 추가할 수 있음
    • WAI의 type Middleware = Application -> Application처럼 동작 변환을 합성 가능하게 만드는 패턴이 운영상 매우 유용함
  • Monoid로 합성되는 인터셉터

    • 미들웨어와 interceptor 타입은 대개 Semigroup과 Monoid 인스턴스를 가질 수 있음
    • WAI의 Middleware는 endomorphism이고, endomorphism은 합성과 id 아래 monoid를 형성함
    • interceptor hook 레코드는 필드별로 합성할 수 있어 tracing, timeout, task queue rewrite 같은 관심사를 별도 배관 없이 mconcat으로 합칠 수 있음 appTemporalInterceptors = mconcat [ retargetingInterceptor , otelInterceptor , sentryInterceptor , sqlApplicationNameInterceptor , loggingContextInterceptor , statementTimeoutInterceptor , teamNameInterceptor , clientExceptionInterceptor , workflowTypeNameInterceptor ]
    • 각 interceptor는 독립 모듈에서 한 가지 관심사만 다루고, mempty에서 필요한 필드만 override하며, 순서는 리스트에 명시됨
  • 이펙트 시스템

    • effectful, polysemy, fused-effects, cleff 같은 effect system도 다른 경로를 제공함
    • 사용 가능한 연산을 effect 타입으로 정의하고 production, testing, tracing용 interpreter를 호출 지점에서 바꿀 수 있음
    • effect를 가로채 메트릭 기록이나 지연 주입 후 실제 handler로 다시 보낼 수 있음
    • 단점은 타입 수준 effect list, handler stack, 까다로운 타입 오류 같은 장치가 추가된다는 것임
    • 함수 레코드는 새 엔지니어가 오후 하나면 이해할 수 있을 만큼 단순함
  • persistent의 긍정적 예

    • persistent의 SqlBackend는 connPrepare, connInsertSql, connBegin, connCommit, connRollback 같은 함수 레코드임
    • OpenTelemetry 계측을 추가할 때 관련 필드를 감싸 모든 데이터베이스 작업에 tracing span을 붙일 수 있었음
    • fork 없이, 거의 소스 변경 없이 데이터베이스 계층 가시성을 확보함
  • 운영상 어려운 라이브러리

    • Mercury는 Hackage에 공개된 웹 API 클라이언트 바인딩을 거의 쓰지 않음
    • 서드파티 바인딩이 구체 함수로 HTTP 호출을 수행하면 tracing, SLO에 맞춘 timeout, 파트너 장애 시뮬레이션, trace의 400ms 공백 설명이 어려워짐
    • 그래서 직접 클라이언트를 작성하고 처음부터 관측 가능하게 만듦
  • 작은 생태계의 비용

    • 일부 Haskell 라이브러리는 버려진 것은 아니지만, 명확히 책임지고 빠르게 개선하는 주체가 없는 공공 인프라처럼 남아 있음
    • 오래된 인터페이스가 유지되고, 관측 가능성·경계 설계·운영성에 관한 새로운 설계를 받아들이는 속도가 느릴 수 있음
    • http-client는 직접적으로 HTTP/1.1만 지원하며, 충분히 쓸 만하지만 특정 시점에는 우회가 필요할 수 있음

패키지 작성자를 위한 운영상 요구

  • 라이브러리 작성자는 사용자가 소스 수정 없이 동작을 주입할 수 있도록 함수 레코드, effect 타입, callback 같은 탈출구를 제공해야 함
  • hs-opentelemetry-api를 의존성으로 추가하고 핵심 IO 작업 주변에 span을 두는 것만으로도 production에서 라이브러리를 운영하는 사용자에게 도움이 됨
    • API 패키지는 breaking change에 보수적이며, 애플리케이션이 OpenTelemetry SDK를 초기화하지 않으면 inert하게 동작하도록 설계됨
    • 성능 오버헤드는 최소화되어 있고, 사용자 애플리케이션에서 예기치 않은 예외나 logging을 발생시키지 않음
    • 의존성 footprint는 아직 원하는 만큼 작지 않으며 개선 작업 중임
  • 라이브러리 코드에서 직접 로그를 쓰면 안 됨
    • logging framework를 import해 stdout이나 stderr에 직접 쓰는 대신 callback, logger parameter, 호출자가 라우팅할 수 있는 로그 메시지 데이터 타입을 제공해야 함
    • 로그가 어디로 가는지는 애플리케이션의 운영 환경에 속한 결정임
    • Mercury는 구조화 로그 파이프라인을 observability stack으로 보내며, 라이브러리가 stderr에 직접 쓰면 JSON lines 스트림과 별도 배관이 필요해짐
  • .Internal 모듈 노출도 고려할 수 있음
    • 사용자가 내부 API에 의존해 refactor가 어려워질 수 있다는 우려는 타당함
    • 하지만 공개 API가 모든 사용 사례를 이미 맞혔다는 확신은 드물게만 정당화됨
    • 명시적 안정성 경고가 있는 .Internal 모듈은 사용자가 패키지를 fork하고 vendoring하는 것보다 나을 수 있음
    • containers, text, unordered-containers는 Haskell 생태계에서 이런 방식을 쓰는 좋은 예임
    • 다만 사용자가 조용히 내부 모듈을 써서 필요한 것을 해결하면 공개 API 결함에 대한 feedback이 줄어들 수 있음

타입에 넣지 않는 것들

  • 프로덕션 Haskell에도 아름답지 않은 부분이 존재함
  • unsafePerformIO는 일상적으로 의존하는 라이브러리 내부에서 쓰임
    • bytestring과 text는 내부적으로 가변 버퍼를 할당하고, 쓰고, freeze해 결과를 만듦
    • 타입은 생성 중 어떤 일이 있었는지 말하지 않음
    • 경계는 관례, 신중한 reasoning, 코드 리뷰로 유지됨
  • 타입 안전한 대안이 성능이나 복잡성 비용을 지나치게 만들면 직접 이런 타협을 작성할 수도 있음
    • 타입이 확인하지 않는 불변식을 문서화해야 함
    • 불편함을 유지하고, 타입 안전한 대안이 실용적이 됐는지 주기적으로 재검토해야 함
    • production Haskell은 타협 부재가 아니라 타협의 규율 있는 격리
  • Hackage의 많은 Haskell 라이브러리에는 테스트가 적거나 없음
    • “컴파일되면 동작한다”는 생각은 작은 순수 코드와 강한 타입에서는 가끔 맞을 수 있음
    • IO-heavy 코드, 외부 시스템 연동, 구조보다 의미에 버그가 있는 코드에는 거의 맞지 않음
  • 타입은 Either ParseError Transaction을 반환한다는 것은 말할 수 있지만 다음은 말할 수 없음
    • amount 필드를 센트로 파싱하는지 달러로 파싱하는지
    • 파트너 API가 생략된 필드와 null 필드를 다르게 해석하는지
    • 재시도 로직이 윤일의 특정 타이밍 창에서 이중 과금을 일으키는지
  • production에서는 이런 라이브러리 위에 시스템을 만들며, 검증되지 않은 가정을 물려받으므로 자기 계층의 integration test로 보완해야 함
  • orphan instance, 문맥상 total이라고 믿는 partial function, 도달 불가능하다고 약속한 error, 어색한 FFI wrapper, 수작업 exception hierarchy 같은 타협도 누적됨
  • 목표는 도덕적 순수성이 아니라, 모든 타협이 어디에 있고 왜 만들어졌으며 제거하면 무엇이 깨지는지 코드 리뷰, 문서, 예제, 테스트로 알 수 있게 하는 것임

Haskell을 프로덕션에서 쓸 가치

  • Haskell은 첫날부터 빠른 선택은 아님
    • 현재 생태계는 Next.js나 Rails처럼 batteries-included hot-reloading 개발 환경을 즉시 제공하지 못함
    • 필요한 라이브러리가 없거나, 있어도 한 사람이 spare time에 유지할 수 있음
    • 오류 메시지가 매우 난해할 때가 있음
  • 채용 문제는 과장되어 있음
    • Mercury CTO Max Tagher는 backend Haskell engineer가 Mercury 전체에서 가장 채용하기 쉬운 역할이라고 공개적으로 말한 바 있음
    • Haskell 일자리에 대한 수요가 공급보다 많아 일반적인 채용 역학이 뒤집힘
    • Mercury는 Haskell 경험이 깊은 사람과 전혀 없는 사람을 모두 채용하며, 후자는 6~8주 교육 프로그램으로 생산성을 갖추게 함
    • 내일 Haskell 전문가 100명이 필요하다면 채용 풀 문제는 현실적이지만, 좋은 범용 개발자를 뽑아 가르칠 의지가 있다면 덜 현실적임
  • 더 큰 채용 리스크는 풀의 크기가 아니라 성향
    • Haskell은 정확성과 추상화에 신경 쓰고 논문을 즐겨 읽으며 기존 가정을 의심하는 이상주의자를 끌어들임
    • 이 강점이 통제되지 않으면 production 책임이 될 수 있음
    • 데이터베이스 계층을 새로운 타입 수준 관계대수 인코딩으로 다시 쓰려 하거나, throwaway script의 String 대신 Text를 쓰지 않았다고 merge를 거부하거나, 모든 설계를 최신 논문식 total rewrite로 끌고 가는 태도는 팀을 느리게 함
  • production Haskell에는 실용주의 문화가 필요함
    • 타입 시스템은 전동 공구이지 종교가 아님
    • 이미 좋은 해법이 있는 문제를 새 메커니즘 발명 기회로 삼는 것은 production에 맞지 않음
  • 수익은 시간이 지나며 나타남
    • 동적 타입 코드베이스에서 몇 주 걸릴 리팩터링이, 타입 변경 후 컴파일러가 모든 call site를 알려줘 몇 시간에 끝날 수 있음
    • 새 엔지니어가 타입 시그니처를 읽고 모듈의 계약을 이해할 수 있음
    • 불가능한 상태가 실제로 표현 불가능해서 production incident가 발생하지 않을 수 있음
  • Mercury는 투자 회수가 몇 년이 아니라 몇 달 단위로 나타난다고 봄
    • 특히 금융 서비스에서는 데이터 무결성 버그 비용이 사용자 불만이 아니라 규제 지적과 타인의 돈으로 측정됨
    • 타입 시스템이 위험을 제거하지는 않지만, 빠르게 성장하는 코드베이스에서 실수로 위험을 도입하기 어렵게 만드는 도구를 제공함
  • Haskell의 production 가치는 은탄환이나 도덕적 운동이 아니라, 다양한 Haskell 숙련도를 가진 팀도 위험한 장치를 경계 안에 두고, 운영 지식을 보존하며, 안전한 경로를 쉬운 경로로 만들 수 있게 하는 강력한 도구 집합에 있음
Read Entire Article