TS로 대수적 데이터 타입(ADT) 구현하기

한상욱·2023년 10월 29일
2

ADT

목록 보기
1/1

요즘 제가 다니는 회사에서 ADT를 바탕으로 타입 설계를 합니다. 따라서 ADT가 어떤 것인지 정확히 알고 넘어가보자 합니다.

1. 대수는 무엇일까?

먼저, 대수적 데이터 타입(Algebraic Data Type)을 이해하기 위해서는 대수가 무엇인지 알고 가야 합니다. 대수는 대수학에서 따온 단어입니다.

대수학은 수가 아닌 친구들을 수처럼 다루는 수학의 한 분야입니다. 다음 예를 봅시다.

1. 3 * x = 6
2. 3 / 3 * x = 6 / 3
3. x = 2

여기서 우리는 숫자 2를 x로 대신하여 문자를 수로 표현했습니다. 따라서 대수적 타입을 풀어서 쓴다면 수가 아닌 타입을 수처럼 사용하는 타입 시스템이라고 이해할 수 있습니다.

2. ADT 타입의 대표적인 두 가지

ADT를 대표하는 타입으로는 합 타입과 곱 타입이 있습니다.

2-1. 합 타입

합 타입은 여러 개의 타입이 or로 묶인 타입입니다. 합 타입은 하위 타입들이 표현할 수 있는 모든 타입을 자신으로 표현할 수 있습니다

예를 들어 색깔이라는 타입은 여러 하위의 색깔들을 가질 수 있습니다.

type 색깔 = 빨강 | 초록 | 파랑;

각각의 빨강, 초록, 파랑도 하위로 특정한 색깔들을 가질 수 있습니다.

type 빨강 = 장미 | 홍당무 | 진빨강;
type 파랑 = 하늘 | 바다;
type 초록 = 잔디 || 국방;

따라서 색깔이라는 타입은 3 + 2 + 3 = 8가지의 색을 표현할 수 있습니다.

2-2. 곱 타입

곱 타입은 여러 개의 타입이 and 연산으로 묶인 타입입니다.

예를 들어 카페 메뉴라는 타입을 과일 타입과 디저트 종류 타입을 이용한 튜플로 표현하면 다음과 같습니다.

type 과일 = 딸기 | 사과 | 바나나;
type= 케잌 | 쉐이크 | 타르트 | 파이;

type 카페메뉴 = [과일,]

따라서 카페메뉴는 3 * 4 = 12가지의 메뉴를 표현할 수 있습니다.

3. 레코드를 이용한 곱 타입 표현하기

위에서 표현한 곱 타입은 튜플을 이용했습니다. 하지만 튜플로 생성한 타입은 TS에서 인덱스를 이용해 데이터에 접근하기 때문에 개발자 입장에서 불편합니다.

const menu: 카페메뉴 = ['딸기', '타르트'];

// '딸기' 가지고 오고 싶음
const fruit = menu[0];

// '타르트' 가지고 오고 싶음
const bread = menu[1];

따라서 TS에서는 레코드를 이용하여 곱 타입을 표현하면 12가지의 메뉴를 모두 표현하면서 데이터도 직관적으로 가져올 수 있게 됩니다.

type 카페메뉴 = {
  fruit: 과일;
  bread:;	
};

const menu: 카페메뉴 = {
  fruit: '딸기',
  bread: '타르트'
};


// '딸기' 가지고 오고 싶음
const fruit = menu.fruit;

// '타르트' 가지고 오고 싶음
const bread = menu.bread;

4. 실제로 어떻게 쓰면 될까

개발하면서 api 응답을 다루다 보면 다음과 같은 경우가 있습니다.

// 공급자 유저
{
  userType: '공급자',
  sellorName: 'XX유통',
  deliverConut: 100,
}

// 소비자 유저
{
  userType: '소비자',
  buyerName: 'YY분식',
  hotPlaceRank: 1,
}

총 우리가 다루어야 하는 유저는 2가지 타입의 유저이고 userType에 따라 가지고 있는 필드의 종류가 서로 다릅니다. 그치만 두 가지 모두 결과값으로 존재할 수 있습니다.

이련 경우 우리는 합타입을 다음과 같이 나타낼 수 있습니다.

type 공급자 = {
  userType: '공급자';
  sellorName: string;
  deliverConut: number;
};

type 소비자 = {
  userType: '소비자';
  buyerName: string;
  hotPlaceRank: number;
};

type 유저 = 공급자 | 소비자;

이런 타입을 설계하면 우리는 아래와 같은 방법으로 응답을 처리할 수 있게 됩니다.

const parseUser = (user: 유저) => 
  switch(user.userType) {
    case '공급자':
      // 공급자가 필요한 비즈니스 로직
      // buyerName, hotPlaceRank 접근 불가
    case '소비자'
      // 소비자가 필요한 비즈니스 로직
      // sellorName, deliverCount 접근 불가
  }

이렇게 되면 서로 다른 타입의 없는 데이터에 접근할 수 없음이 보장이 되므로 안전한 개발을 할 수 있게 됩니다.

5. 결론

우리는 ADT를 기반으로 코드를 작성할 때, 타입 시스템을 더욱 견고히 만들 수 있으며 사람이 낼 수 있는 실수를 런타임이 아님 컴파일 타임에 잡을 수 있게 됩니다.

또한 ADT를 활용하면 함수형 프로그래밍이나 패턴 매칭도 더욱 쉽게 사용할 수 있게 됩니다. 이들은 추후 다른 글에서 다뤄 보겠습니다. 감사합니다.

profile
그냥 뛰는 사람

0개의 댓글