JavaScript

패턴 매칭(Pattern Matching) in JavaScript

La.place 2019. 9. 30. 16:48

TC-39 Stage 1 - Pattern Matching

 

 TC-39 Github에 패턴 매칭(Parttern-Matching)이 Stage 0에서 1로 한단계 등급이 상향됬었다. Stage1은 proposal 단계로 앞으로 TC-39 위원회에서 논의할 예정이며, 구체적인 데모에 대한 코드도 포함되어야 한다. 아직 Stage2(draft)와 Stage3(candidate), 그리고 stage4(finished)를 거쳐 정식으로 스펙에 추가되려면 갈 길이 멀지만 어떤 기능을 가지고 있고 또 어떻게 사용할 수 있는지 간단하게나마 미리 알아보도록 하자.

패턴 매칭은 Scala, F#, Rust 등 다른 언어에도 있는 기능인데, 앞서 언급한 언어에 대해서는 잘 모르지만, 비교적 단순하므로 다른 언어를 통해 알아보도록 하자.

Scala

object MatchTest1 extends App {
  def matchTest(x: Int): String = x match {
    case 1 => "one"
    case 2 => "two"
    case _ => "many"
  }
  println(matchTest(3))
}

case명령문을 포함하고 있는 블록은 정수를 문자열로 매핑하는 함수를 정의한다.match라는 키워드를 사용해서 x의 값이 1이 들어올 경우 "one", 2가 들어올 경우 "two", 그 외 값이들어올 경우는 "many"를 반환한다.

Rust

match x {
    1 => println!("one"),
    2 => println!("two"),
    3 => println!("three"),
    _ => println!("anything"),
}

 

Rust도 크게 차이는 없다. 물론 | operator라던가 struct도 패턴 매칭이 가능하다.

let x = 1;

match x {
    1 | 2 => println!("one or two"),
    3 => println!("three"),
    _ => println!("anything"),
}
struct Point {
    x: i32,
    y: i32,
}

let origin = Point { x: 0, y: 0 };

match origin {
    Point { x, y } => println!("({},{})", x, y),
}

여기까지만 본다믄 Switch-Case와 비슷하게 보이는데, 패턴 매칭은 그 쓰임새가 더 용이하다. 아래는 TC-39에서 제안된 패턴매칭의 예제이다. case, when 그리고 if를 사용해서 직관적으로 컨디션 처리를 할 수 있다. 또 적절하게 구조 분해 할당(Destructuring)을 사용한 것을 볼 수 있다.

const res = await fetch(jsonService)
case (res) {
  when {status: 200, headers: {'Content-Length': s}} ->
    console.log(`size is ${s}`),
  when {status: 404} ->
    console.log('JSON not found'),
  when {status} if (status >= 400) -> {
    throw new RequestError(res)
  },
}

자바스크립트를 이용하여 서버와 통신할 때, 서버의 응답 값에 따라 어떤 결과처리를 할 때가 종종 있다. 보통 이때 if문이나 Switch-Case를 사용해서 분기처리를 하지만 복잡한 컨디션의 경우 코드가 매우 복잡해진다. 하지만 제안된 패턴매칭 방법을 사용하면 비교적 명료하고 간단하게 처리가 가능하다. 만약 독자가 React를 사용하고 있다면 Redux의 리듀서를 작성하는데도 활용 할 수 있다.

Reducer

function todoApp(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return Object.assign({}, state, {
        visibilityFilter: action.filter
      })
    case ADD_TODO:
      return Object.assign({}, state, {
        todos: [
          ...state.todos,
          {
            text: action.text,
            completed: false
          }
        ]
      })
    case TOGGLE_TODO:
      return Object.assign({}, state, {
        todos: state.todos.map((todo, index) => {
          if (index === action.index) {
            return Object.assign({}, todo, {
              completed: !todo.completed
            })
          }
          return todo
        })
      })
    default:
      return state
  }
}

Reducer with parttern matching

function todoApp (state = initialState, action) {
  return case (action) {
    when {type: 'set-visibility-filter', filter: visFilter} ->
      ({...state, visFilter}),
    when {type: 'add-todo', text} ->
      ({...state, todos: [...state.todos, {text}]}),
    when {type: 'toggle-todo', index} -> (
      {
        ...state,
        todos: state.todos.map((todo, idx) => idx === index
          ? {...todo, done: !todo.done}
          : todo
        )
      }
    )
    when _ -> state // ignore unknown actions
  }
}

 

또 자바스크랩트 내에서만 한정하지 않고, JSX안에도 다음과 같이 활용할 수 있다.

<Fetch url={API_URL}>{
  props => case (props) {
    when {loading} -> <Loading />
    when {error} -> <Error error={error} />
    when {data} -> <Page data={data} />
  }
}
</Fetch>

 

마지막으로 함수 덕 타이핑(duck-typing)에도 활용이 가능하니, 익숙해진다면 다양한 곳에 활용할 수 있을 듯 하다.

const getLength = vector => case (vector) {
  when { x, y, z } -> Math.sqrt(x ** 2 + y ** 2 + z ** 2)
  when { x, y } -> Math.sqrt(x ** 2 + y ** 2)
  when [...etc] -> vector.length
}
getLength({x: 1, y: 2, z: 3}) // 3.74165

마치며

물론, 현재는 Stage1 단계라 추후 API 명세가 크게 바뀔 가능성도 있다(Stage0에서는 없었던 when이 생겼던 것처럼). 현재 바벨에도 추가하려는 움직임이 보이고 있으니 생각보다 우리가 빠르게 접하게 될 것이라 생각한다(#9010), (#9318). 개인적으로 Optional Chaining과 더불어 어서 추가됬으면 좋겠다는 생각이 든다. 참고로 패턴 매칭을 개념을 이용한 유틸 함수들은 이미 많이 만들어져 활용되고 있으므로, 시간이 된다면 직접 만들어서 활용해 보면 좋을 것 같다.

'JavaScript' 카테고리의 다른 글

React Fast Refresh  (0) 2021.01.10
Spread Syntax와 Rest Parameter  (0) 2019.11.03
Beginning Javascript Iterator and Generator  (2) 2019.01.12
Why React Hooks  (0) 2019.01.05
Javascript Optional Chaining  (1) 2018.11.10