case, cond 그리고 if
case, cond 그리고 if
이번 장에서는 Elixir의 제어 흐름 구조인 case, cond, if를 배워볼게요. 세 가지가 언제 어떤 상황에서 유용한지, 그리고 각각의 동작 원리를 하나씩 짚어보면서 자연스럽게 익힐 수 있도록 설명드릴게요.
출처: Elixir 공식 가이드
case
case는 값을 여러 패턴과 차례대로 비교해서, 처음으로 일치하는 패턴을 찾는 구조예요.
iex> case {1, 2, 3} do
...> {4, 5, 6} ->
...> "This clause won't match"
...> {1, x, 3} ->
...> "This clause will match and bind x to 2 in this clause"
...> _ ->
...> "This clause would match any value"
...> end
"This clause will match and bind x to 2 in this clause"
이미 존재하는 변수에 대해 패턴 매칭을 하고 싶다면 ^ 연산자를 써야 해요. ^x처럼 쓰면 "이 위치에서 변수 x에 새 값을 바인딩하지 말고, x가 가진 현재 값과 비교해 줘"라는 뜻이 돼요.
iex> x = 1
1
iex> case 10 do
...> ^x -> "Won't match"
...> _ -> "Will match"
...> end
"Will match"
case의 절에는 가드(guard)를 붙여서 추가 조건을 지정할 수도 있어요.
iex> case {1, 2, 3} do
...> {1, x, 3} when x > 0 ->
...> "Will match"
...> _ ->
...> "Would match, if guard condition were not satisfied"
...> end
"Will match"
위 예시에서 첫 번째 절은 x > 0일 때만 매치돼요.
가드 안에서 발생하는 오류는 밖으로 새지 않고, 그냥 가드가 실패한 것으로 처리된다는 점도 기억해 두세요.
iex> hd(1)
** (ArgumentError) argument error
iex> case 1 do
...> x when hd(x) -> "Won't match"
...> x -> "Got #{x}"
...> end
"Got 1"
어떤 절도 매치되지 않으면 오류가 발생해요.
iex> case :ok do
...> :error -> "Won't match"
...> end
** (CaseClauseError) no case clause matching: :ok
사용할 수 있는 모든 가드는 Kernel 모듈 문서의 사이드바에서 확인할 수 있어요. 더 깊이 있는 내용은 Patterns and Guards 참고 문서도 함께 보면 좋아요.
if
case는 패턴 매칭과 가드 위에 기반을 두고 있어요. 그런데 패턴과 가드는 컴파일러가 최적화할 수 있는 특정 표현식들로 제한돼요. case로는 표현하기 어려운 조건을 써야 하는 상황이 많죠. 그럴 때 유용한 대안이 if예요.
iex> if true do
...> "This works!"
...> end
"This works!"
iex> if false do
...> "This will never be seen"
...> end
nil
if에 주어진 조건이 false나 nil을 돌려주면, do-end 사이의 본문은 실행되지 않고 대신 nil을 돌려줘요.
if는 else 블록도 지원해요.
iex> if nil do
...> "This won't be seen"
...> else
...> "This will"
...> end
"This will"
표현식 (Expressions)
어떤 언어는 값을 돌려주는 표현식(expression)과 값을 돌려주지 않는 문(statement)을 구분해요. 그런데 Elixir에는 문이 없고 표현식만 있어요. Elixir에서 작성하는 모든 것은 값을 돌려줘요.
이 성질 덕분에 변수를 if, case 같은 개별 코드 블록 안으로 한정할 수 있어요. 블록 안에서 선언하거나 바꾼 변수는 그 블록 안에서만 보이고, 바깥 블록으로 새지 않기 때문에 코드를 따라가기 쉬워요. 예를 들어볼게요.
iex> x = 1
1
iex> if true do
...> x = x + 1
...> end
2
iex> x
1
여기서 if 표현식의 반환값이 2로 나오는 걸 볼 수 있어요. if 안에서 바꾼 값을 바깥 블록에 남기고 싶다면, 반환된 값을 바깥 블록의 변수에 다시 할당해야 해요.
iex> x = 1
1
iex> x =
...> if true do
...> x + 1
...> else
...> x
...> end
2
모든 표현식이 값을 돌려주기 때문에, if의 대안으로 쓰이는 삼항 연산자 같은 별도의 구문도 필요 없어요. Elixir에는 if의 인라인 표기법도 있는데, 이건 나중에 배울 if의 인자에 대한 문법적 변형이에요.
if는 매크로예요
흥미로운 점 한 가지, if는 언어에서 특별하게 예약된 구문이 아니라 매크로로 구현되어 있어요. 많은 언어에서는 언어 자체의 특수한 구문으로 다루지만, Elixir는 그렇게 하지 않아요. 자세한 내용은 문서와 소스 코드에서 확인할 수 있어요.
if를 여러 겹 중첩해서 쓰고 있다면, cond를 고려해 보는 게 좋아요. 바로 확인해 볼게요.
cond
case로 여러 패턴 중에서 매치되는 것을 찾고, if로 단일 조건을 검사했죠. 이번엔 여러 조건을 차례로 검사하면서 nil이나 false가 아닌 첫 번째 조건을 찾아야 할 때예요. 그럴 때 유용한 구조가 cond예요.
iex> cond do
...> 2 + 2 == 5 ->
...> "This will not be true"
...> 2 * 2 == 3 ->
...> "Nor this"
...> 1 + 1 == 2 ->
...> "But this will"
...> end
"But this will"
이건 많은 명령형 언어의 else if 절과 비슷하지만, Elixir에서는 자주 쓰이진 않아요.
모든 조건이 nil이나 false를 돌려주면 CondClauseError 오류가 발생해요. 그래서 마지막에 항상 매치되는 true 조건을 하나 추가해 두는 게 좋을 때가 있어요.
iex> cond do
...> 2 + 2 == 5 ->
...> "This is never true"
...> 2 * 2 == 3 ->
...> "Nor this"
...> true ->
...> "This is always true (equivalent to else)"
...> end
"This is always true (equivalent to else)"
if와 마찬가지로 cond도 nil과 false를 제외한 모든 값을 true로 취급해요.
iex> cond do
...> hd([1, 2, 3]) ->
...> "1 is considered as true"
...> end
"1 is considered as true"
정리 (Summing up)
이상으로 Elixir에서 가장 기본적인 제어 흐름 구조를 모두 살펴봤어요. 일반적으로 Elixir 개발자들은 패턴 매칭과 가드를 더 선호해요. case나 함수 정의(다음 장들에서 다룰 예정)가 간결하고 정확하기 때문이에요. 로직이 패턴과 가드로 표현되지 않을 때 if를, 검사해야 할 조건이 여러 개라면 cond를 사용하면 돼요.
더 알아보기
- Patterns and Guards —
case가 기반으로 하는 패턴 매칭과 가드에 대한 심화 문서예요. Kernel모듈 문서 — 사용할 수 있는 모든 가드와 내장 함수를 확인할 수 있어요.- Keywords and Maps — 이 장의
do-end블록과 연결되는 키워드 문법을 나중에 더 자세히 다뤄요.