Conceptly
← 전체 목록
🧩

Algebraic Data Type

데이터 흐름값의 가능한 모양을 닫아 두는 타입 모델

ADT(Algebraic Data Type)는 값이 가질 수 있는 모양을 몇 가지 변형으로 닫아 두고, 각 변형에 필요한 데이터만 정확히 붙여 두는 모델링 방식입니다. 여기서 algebraic이라는 말은 합(sum)과 곱(product)이라는 두 조합 방식에서 옵니다. 곱 타입은 여러 필드가 함께 존재하는 구조이고, 합 타입은 여러 변형 중 정확히 하나만 선택되는 구조입니다. ADT는 이 둘을 조합해 '이 값이 가질 수 있는 상태는 여기까지'를 타입으로 못 박아 줍니다.

아키텍처 다이어그램

🔍 구조 다이어그램

Diagram preview

The interactive diagram loads after the page becomes ready.

점선 애니메이션은 데이터 또는 요청의 흐름 방향을 나타냅니다

왜 필요한가요?

상태를 boolean 플래그 몇 개와 optional 필드 조합으로 표현하면, 실제로는 나오면 안 되는 모순된 조합이 너무 쉽게 생깁니다. isLoading = true인데 동시에 dataerror가 다 들어 있는 값이 대표적입니다. 그러면 읽는 사람은 어떤 조합이 진짜 가능한 상태인지 머리로 외워야 하고, 검증 로직도 끝없이 늘어납니다. 결국 문제는 데이터 모델이 상태 기계를 제대로 표현하지 못하고 있다는 데 있습니다. 애초에 불가능한 상태를 만들 수 없게 막아 주는 모델이 필요합니다.

왜 이런 방식이 등장했나요?

함수형 프로그래밍 언어들은 오래전부터 '모델이 정확하면 로직이 단순해진다'는 감각을 ADT로 밀어 왔습니다. Haskell, Elm, OCaml, F# 같은 언어에서는 custom type이나 union type이 핵심 문법이고, Scala와 Rust도 같은 아이디어를 언어 중심에 두고 있습니다. 이 접근은 웹 개발과 앱 개발에서도 강해졌습니다. UI가 로딩/성공/실패처럼 분명한 상태를 오가는데, 이를 느슨한 객체 하나로 표현하면 런타임 분기가 늘어납니다. 예를 들어 const state = { isLoading: true, data: user, error: "timeout" } 같은 객체는 로딩, 성공, 실패 정보가 한 값 안에 동시에 섞이는 모순을 허용합니다. 타입이 '이 조합은 불가능하다'고 막아 주지 못하니, 소비하는 쪽은 매번 방어적으로 if문을 늘려야 합니다. ADT로 상태 공간을 닫아 두면 로직은 '어떤 변형인가'만 물으면 되므로 코드가 훨씬 선명해집니다.

내부적으로 어떻게 동작하나요?

ADT는 보통 두 층으로 이해하면 쉽습니다. 첫째, product는 하나의 상태 안에서 함께 존재해야 하는 필드 묶음입니다. 예를 들어 Success 상태에는 datareceivedAt가 같이 들어갈 수 있습니다. 둘째, sum은 가능한 상태 후보들의 집합입니다. Loading | Success | Error처럼 값은 이들 중 정확히 하나만 가질 수 있습니다. 이 두 층을 합치면, 각 상태가 어떤 데이터를 가져야 하는지와 어떤 상태들이 가능한지를 동시에 정의할 수 있습니다. 이렇게 닫힌 모델을 만들면 호출자는 값의 태그만 보고 어떤 필드가 안전하게 존재하는지 즉시 알 수 있고, 패턴 매칭은 그 사실을 코드 수준에서 활용하게 해 줍니다.

코드로 보면

닫힌 상태 공간을 코드로 표현하기

type User = { name: string };

type RemoteData<T> =
  | { kind: "idle" }
  | { kind: "loading" }
  | { kind: "success"; data: T; receivedAt: number }
  | { kind: "error"; message: string; retryable: boolean };

const loading: RemoteData<User> = { kind: "loading" };

const success: RemoteData<User> = {
  kind: "success",
  data: { name: "준영" },
  receivedAt: Date.now(),
};

// 불가능한 조합은 타입에서 막힘
// const broken: RemoteData<User> = {
//   kind: "loading",
//   data: { name: "준영" },
//   message: "timeout",
// };

`kind`가 가능한 상태 집합을 닫아 두고, 각 변형은 자기에게 필요한 payload만 가집니다. loading에는 data가 없고 success에는 data가 반드시 있어야 한다는 규칙이 타입에서 바로 드러납니다.

경계와 구분

ADT와 패턴 매칭은 항상 같이 언급되지만 역할이 다릅니다. ADT는 데이터를 어떻게 모델링할지에 대한 규칙이고, 패턴 매칭은 그렇게 모델링한 값을 어떻게 소비할지에 대한 도구입니다. ADT 없이 패턴 매칭만 있어도 분기할 수는 있지만, 모델이 느슨하면 분기 근거도 약해집니다. 일반 객체나 record 타입과도 구분이 필요합니다. 객체 하나에 선택 필드를 계속 덧붙이는 방식은 유연하지만 상태 공간이 열려 있어서 불가능한 조합을 막기 어렵습니다. ADT는 유연성을 조금 줄이는 대신 가능한 상태를 명시적으로 닫아 두어, 로직과 검증 비용을 낮춥니다.

언제 쓰나요?

ADT는 UI 상태, 비동기 로딩 흐름, 명령/이벤트 모델, 파서 결과, 프로토콜 메시지처럼 가능한 모양이 분명한 데이터를 다룰 때 특히 강합니다. 실무에서는 RemoteData = Idle | Loading | Success | Error 같은 식으로 시작해 화면 분기와 비즈니스 규칙을 크게 단순화하곤 합니다. '이 경우엔 이 필드가 반드시 있고, 저 경우엔 없어야 한다'는 제약을 타입에 담아 두면, 런타임 if문으로 하던 유효성 검사를 모델링 단계에서 많이 줄일 수 있습니다. 그래서 ADT는 문법 장식이라기보다 복잡한 상태 공간을 작게 접어 두는 설계 도구에 가깝습니다.

UI 상태 모델링도메인 이벤트워크플로 상태 머신API 응답 모델