패턴 매칭: `=` 연산자와 핀 연산자 `^`
패턴 매칭: = 연산자와 핀 연산자 ^
엘릭서에서 =는 단순한 값 대입이 아니라, 왼쪽과 오른쪽을 서로 맞춰보는 매치(match) 연산자예요. 이 연산자를 이해하면 튜플·리스트 같은 구조 안에서 값을 끄집어내는 분해(destructuring)가 자연스러워져요. 뒤에서 배울 재귀와 여러 언어 구성의 기초가 되니, 코드가 말하는 흐름을 하나씩 따라가 보면 좋아요.
본문
매치 연산자
엘릭서에서 변수에 값을 넣을 때 =를 써봤을 거예요.
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)로 더 확장할 수도 있어요.