만베거
newminkyung
15 1

타입스크립트 패턴 매칭

패턴매칭

  • 특정 패턴에 맞는 데이터를 찾거나 추출하는 기능은 많은 함수형 프로그래밍 언어에서 사용되는 개념
  • Haskell, OCaml, Erlang, Rust, Swift, Elixir, Rescript 등 많은 언어에서 이 기능을 지원
  • 함수형 프로그래밍은 데이터를 조작하고 처리하기 위해 선언적인 방식을 채택
  • 패턴 매칭이라는 메커니즘을 사용하여 특정 패턴을 가진 데이터를 찾거나 추출
  • 패턴 매칭을 사용하여 리스트에서 특정 요소를 찾거나
  • 특정 데이터 구조에서 원하는 값을 추출하는 등

Rust에서 패턴매칭

  • match 키워드를 사용
fn main() {
    let p = Point { x: 0, y: 7 };
 
    match p {
        Point { x, y: 0 } => println!("x값은 x축 위에 있습니다. 값: {}", x),
        Point { x: 0, y } => println!("y값은 y축 위에 있습니다. {}", y),
        Point { x, y } => println!("좌표값: ({}, {})", x, y),
    }
}
  • Point 구조체의 인스턴스 p를 여러 패턴으로 매칭시켜 해당 패턴에 따라 결과를 출력

패턴매칭 vs 조건문

주어진 조건 또는 패턴에 따라 동작을 분기시킨다는 역할이 동일하다 하지만 이 둘은 동작하고 사용되는 상황이 다르다

  • 패턴매칭
    • 데이터의 구조나 패턴을 중심으로 동작
    • 데이터의 패턴 정의
    • 주어진 패턴이나 구조와 비교하여 일치하는 경우에 특정 동작을 수행
      • 주로 문자열, 리스트, 튜플 등과 같은 데이터 구조에서 패턴을 비교하고, 패턴에 맞는 데이터를 추출하거나 처리하는 데 사용
  • 조건문
    • 주어진 조건에 따라 프로그램의 흐름을 분기
    • 주어진 조건이 참인 경우, 특정 코드 블록을 실행
    • 조건이 거짓인 경우, 다른 코드 블록을 실행하거나 흐름을 제어

-> 패턴 매칭과 조건문은 목적과 사용 방식에서 차이가 있지만, 두 가지 모두 프로그램의 제어 흐름을 분기시키는 데 사용되는 기능

자바스크립트에서의 패턴 매칭

  • TC39 (opens in a new tab)에 1단계까지 진행됨
  • @babel/plugin-proposal-pattern-matching, xstat, lodash_matchs 를 사용하면 패턴매칭을 자바스크립트에서도 구현할 수 있음

ts-pattern

  • 타입스크립트에서 패턴 매칭을 구현할 수 있게 해주는 라이브러리

예시

import { match } from 'ts-pattern';
 
type Weather = '맑음' | '구름' | '비' | '눈';
 
const getWeatherDescription = (weather: Weather): string => {
  return match<Weather, string>(weather)
    .with('맑음', () => '오늘은 맑은 날이네요! 선크림을 꼭 챙기세요.🕶️')
    .with('구름', () => '오늘은 구름이 낀 날이에요.🌥️')
    .with('비', () => '오늘은 비가 오네요! 꼭 우산을 챙기세요.🌧️')
    .with('눈', () => '오늘은 눈이 오네요! 눈사람 만들 준비가 되셨나요?☃️')
    .exhaustive(() => '유효하지 않은 날씨입니다.');
};
 
const currentWeather = 'sunny';
const weatherDescription = getWeatherDescription(currentWeather);
console.log(weatherDescription); // 오늘은 맑은 날이네요! 선크림을 꼭 챙기세요.🕶️
  • 주어진 weather 값에 따라 해당하는 날씨 설명을 반환
  • match 함수를 사용하여 weather 값과 매칭되는 내용을 선택

또 다른 예시

  • 기존 코드의 문제
declare let fetchState:
  | { status: { label: "loading" }}
  | { status: { label: "success" }, data: string }
  | { status: { label: "error" }, message: string };
 
// switch 사용
switch (fetchState.status.label) {
  case "loading":
    console.log("로딩중..");
    break;
  case "success":
    console.log("성공! 데이터: ", fetchState.data); //type error!
    break;
  case "error":
    console.error("에러: ", fetchState.message); //type error!
    break;
}
 
// if 사용
if (fetchState.status.label === "loading") {
  console.log("로딩중..");
} else if (fetchState.status.label === "success") {
  console.log("성공! 데이터: ", fetchState.data); //type error!
} else if (fetchState.status.label === "error") {
  console.error("에러: ", fetchState.message); //type error!
}
  • if-else로 조건문을 사용한 경우에는 타입 변경에 매우 취약

  • fetchState의 타입에 또 다른 상태(ex. { label: "idle" })가 추가되더라도, 코드에서 어떤 상태에 따라 분기하는 부분에서 에러가 발생하지 않음

  • ts-pattern을 이용하면

match(fetchState)
  .with({ status: { label: "loading" } }, () => console.log("로딩중.."))
  .with({ status: { label: "success" } }, ({ data }) =>
    console.log("성공! 데이터: ", data)
  )
  .with({ status: { label: "error" } }, ({ message }) =>
    console.error("에러: ", message)
  )
  .exhaustive();
  • 타입 추론을 자연스럽게 할 수 있음
  • with 함수의 콜백에는 패턴에 맞는 데이터가 타입 추론이 된 채로 넘어오기 때문