패턴 매칭: `=` 연산자와 핀 연산자 `^`

패턴 매칭: = 연산자와 핀 연산자 ^

엘릭서에서 =는 단순한 값 대입이 아니라, 왼쪽과 오른쪽을 서로 맞춰보는 매치(match) 연산자예요. 이 연산자를 이해하면 튜플·리스트 같은 구조 안에서 값을 끄집어내는 분해(destructuring)가 자연스러워져요. 뒤에서 배울 재귀와 여러 언어 구성의 기초가 되니, 코드가 말하는 흐름을 하나씩 따라가 보면 좋아요.

출처: Pattern matching — Elixir 공식 getting-started 가이드

본문

매치 연산자

엘릭서에서 변수에 값을 넣을 때 =를 써봤을 거예요.

iex> x = 1
1
iex> x
1

그런데 엘릭서의 =는 사실 매치 연산자라고 불러요. 왜 그런지 함께 볼게요.

iex> x = 1
1
iex> 1 = x
1
iex> 2 = x
** (MatchError) no match of right hand side value: 1

여기서 1 = x가 유효한 표현식이라는 점에 주목해요. 왼쪽과 오른쪽이 모두 1이라 서로 맞았기 때문에 통과했죠. 양쪽이 맞지 않으면 MatchError가 발생해요.

변수는 =의 왼쪽에서만 새로 바인딩할 수 있어요.

iex> 1 = unknown
** (CompileError) iex:1: undefined variable "unknown"

패턴 매칭

매치 연산자는 단순한 값뿐 아니라, 더 복잡한 데이터 타입을 분해하는 데도 써요. 튜플에 패턴 매칭을 적용해 보죠.

iex> {a, b, c} = {:hello, "world", 42}
{:hello, "world", 42}
iex> a
:hello
iex> b
"world"

양쪽이 맞지 않으면 패턴 매칭 오류가 나요. 예를 들어 튜플의 크기가 다르면 다음과 같아요.

iex> {a, b, c} = {:hello, "world"}
** (MatchError) no match of right hand side value: {:hello, "world"}

타입이 달라도 오류가 나요. 왼쪽에 튜플, 오른쪽에 리스트를 놓으면 어긋나죠.

iex> {a, b, c} = [:hello, "world", 42]
** (MatchError) no match of right hand side value: [:hello, "world", 42]

더 흥미로운 건 특정 값에 맞춰 매칭하는 경우예요. 아래 예시는 오른쪽이 :ok라는 원자로 시작하는 튜플일 때만 왼쪽 패턴이 맞는다고 선언한 거예요.

iex> {:ok, result} = {:ok, 13}
{:ok, 13}
iex> result
13

iex> {:ok, result} = {:error, :oops}
** (MatchError) no match of right hand side value: {:error, :oops}

리스트에도 패턴 매칭을 쓸 수 있어요.

iex> [a, b, c] = [1, 2, 3]
[1, 2, 3]
iex> a
1

리스트는 자신의 head와 tail에 맞춰 매칭하는 것도 지원해요.

iex> [head | tail] = [1, 2, 3]
[1, 2, 3]
iex> head
1
iex> tail
[2, 3]

hd·tl 함수와 비슷하게, 빈 리스트는 head와 tail 패턴으로 매칭할 수 없어요.

iex> [head | tail] = []
** (MatchError) no match of right hand side value: []

[head | tail] 형식은 패턴 매칭뿐 아니라 리스트 앞에 항목을 추가할 때도 써요.

iex> list = [1, 2, 3]
[1, 2, 3]
iex> [0 | list]
[0, 1, 2, 3]

패턴에서 특정 값을 신경 쓰지 않을 때가 있어요. 그런 값은 관례적으로 밑줄 _에 바인딩해요. 예를 들어 리스트의 head만 중요하다면 tail을 밑줄에 넣으면 되죠.

iex> [head | _] = [1, 2, 3]
[1, 2, 3]
iex> head
1

_ 변수는 특별해서 절대 읽을 수 없어요. 읽으려 하면 컴파일 오류가 나요.

iex> _
** (CompileError) iex:1: invalid use of _. "_" represents a value to be ignored in a pattern and cannot be used in expressions

패턴에서 같은 변수를 두 번 이상 쓰면 모든 참조가 같은 값에 바인딩되어야 해요.

iex> {x, x} = {1, 1}
{1, 1}
iex> {x, x} = {1, 2}
** (MatchError) no match of right hand side value: {1, 2}

패턴 매칭은 강력하지만 제약도 있어요. 예를 들어 매치의 왼쪽에서 함수를 호출할 수 없어요. 다음 예시는 유효하지 않아요.

iex> length([1, [2], 3]) = 3
** (CompileError) iex:1: cannot invoke remote function :erlang.length/1 inside match

패턴 매칭은 튜플·리스트 같은 데이터 타입을 쉽게 분해하게 해줘요. 이어지는 장에서 보겠지만, 엘릭서 재귀의 기초 중 하나이며 맵·바이너리 같은 다른 타입에도 적용돼요.

핀 연산자

엘릭서의 변수는 다시 바인딩될 수 있어요.

iex> x = 1
1
iex> x = 2
2

하지만 변수가 다시 바인딩되는 걸 원하지 않는 순간이 있어요.

변수의 기존 값에 맞춰 패턴 매칭을 하고 싶을 때는 핀 연산자 ^를 사용해요. 그러면 변수를 다시 바인딩하지 않고 값을 비교하죠.

iex> x = 1
1
iex> ^x = 2
** (MatchError) no match of right hand side value: 2

x를 값 1에 바인딩한 채로 핀으로 고정했으니, 아래와 같은 셈이에요.

iex> 1 = 2
** (MatchError) no match of right hand side value: 2

심지어 똑같은 오류 메시지가 나오는 것도 확인할 수 있어요.

핀 연산자는 튜플이나 리스트 같은 다른 패턴 매칭 안에서도 쓸 수 있어요.

iex> x = 1
1
iex> [^x, 2, 3] = [1, 2, 3]
[1, 2, 3]
iex> {y, ^x} = {2, 1}
{2, 1}
iex> y
2
iex> {y, ^x} = {2, 2}
** (MatchError) no match of right hand side value: {2, 2}

x가 핀으로 고정될 때 값 1이었으니, 마지막 예시는 이렇게 쓴 것과 같아요.

iex> {y, 1} = {2, 2}
** (MatchError) no match of right hand side value: {2, 2}

이것으로 패턴 매칭 소개를 마칠게요. 다음 장에서 보겠지만 패턴 매칭은 많은 언어 구성에서 아주 흔하게 쓰이고, 가드(guard)로 더 확장할 수도 있어요.

더 알아보기