Match Expressions
Match Expressions
여러 조건에 따라 실행 흐름을 나누고 싶을 때, F#에서는 값을 하나씩 견주어 보는 match 표현식을 써요. 값을 패턴 여러 개와 차례로 비교해서 처음으로 맞는 패턴의 결과를 돌려주는 구조예요. 복잡한 분기(if-else 체인)를 깔끔하게 정리하고 싶을 때 정말 자주 쓰게 되죠.
본문
문법
// Match expression.
match test-expression with
| pattern1 [ when condition ] -> result-expression1
| pattern2 [ when condition ] -> result-expression2
| ...
// Pattern matching function.
function
| pattern1 [ when condition ] -> result-expression1
| pattern2 [ when condition ] -> result-expression2
| ...
설명
패턴 일치(match) 표현식은 검사할 값(test-expression)과 패턴 모음을 비교해서 복잡한 분기를 만들어 내는 방식이에요. match 표현식에서는 test-expression을 패턴 하나하나와 차례로 비교해요. 어느 하나가 맞는 순간 그에 대응하는 result-expression을 평가하고, 그 결과 값을 match 표현식 전체의 값으로 돌려주죠.
앞선 문법에서 나온 '패턴 일치 함수'는 사실 람다 표현식이에요. 인자에 대해 바로 패턴 일치를 수행하는 형태인데, 아래처럼 쓴 것과 똑같은 의미예요.
fun arg ->
match arg with
| pattern1 [ when condition ] -> result-expression1
| pattern2 [ when condition ] -> result-expression2
| ...
람다 표현식에 대해 더 알고 싶다면 Lambda Expressions: The fun Keyword 문서를 참고해요.
여기서 주의할 점이 하나 있어요. 패턴 전체가 입력 변수로 나올 수 있는 모든 경우를 빠짐없이 덮어야 해요. 그래서 보통 마지막 패턴으로 와일드카드 패턴(_)을 써서, 그 전까지 맞지 않았던 나머지 입력 값을 전부 받아내요.
아래 코드는 match 표현식의 쓰임새를 몇 가지 보여 줘요. 사용할 수 있는 모든 패턴의 참조와 예시는 Pattern Matching 문서에서 확인해요.
let list1 = [ 1; 5; 100; 450; 788 ]
// Pattern matching by using the cons pattern and a list
// pattern that tests for an empty list.
let rec printList listx =
match listx with
| head :: tail -> printf "%d " head; printList tail
| [] -> printfn ""
printList list1
// Pattern matching with multiple alternatives on the same line.
let filter123 x =
match x with
| 1 | 2 | 3 -> printfn "Found 1, 2, or 3!"
| a -> printfn "%d" a
// The same function written with the pattern matching
// function syntax.
let filterNumbers =
function | 1 | 2 | 3 -> printfn "Found 1, 2, or 3!"
| a -> printfn "%d" a
filter123을 보면 | 1 | 2 | 3 -> ...처럼 여러 대안을 같은 줄에 |로 이어서 한 패턴으로 묶을 수 있어요. 그리고 그 아래 filterNumbers는 function 키워드로 같은 로직을 더 짧게 표현한 예시예요.
패턴 가드(Guards on patterns)
when 절을 쓰면 변수가 어떤 패턴에 맞으려면 반드시 만족해야 하는 추가 조건을 지정할 수 있어요. 이 절을 가드(guard)라고 불러요. when 뒤에 오는 표현식은, 그 가드가 붙은 패턴에 실제로 값이 맞았을 때에만 평가돼요. 즉 맞지 않는 값까지 가드를 검사하느라 낭비하지 않죠.
아래 예시는 변수 패턴에 숫자 범위를 지정하기 위해 가드를 쓴 모습이에요. 여러 조건은 부울 연산자로 묶어서 나타낸다는 점도 함께 보여 줘요.
let rangeTest testValue mid size =
match testValue with
| var1 when var1 >= mid - size/2 && var1 <= mid + size/2 -> printfn "The test value is in range."
| _ -> printfn "The test value is out of range."
rangeTest 10 20 5
rangeTest 10 20 10
rangeTest 10 20 40
한 가지 알아둘 점이 있어요. 패턴에는 리터럴(숫자·문자 같은 글자 그대로의 값) 외의 값을 직접 쓸 수 없어요. 그래서 입력의 일부를 어떤 값과 비교해야 한다면 반드시 when 절을 써야 해요. 다음 코드가 그 예시예요.
// This example uses patterns that have when guards.
let detectValue point target =
match point with
| (a, b) when a = target && b = target -> printfn "Both values match target %d." target
| (a, b) when a = target -> printfn "First value matched target in (%d, %d)" target b
| (a, b) when b = target -> printfn "Second value matched target in (%d, %d)" a target
| _ -> printfn "Neither value matches target."
detectValue (0, 0) 0
detectValue (1, 0) 0
detectValue (0, 10) 0
detectValue (10, 15) 0
가드가 공용(union) 패턴에 붙으면 주의해야 해요. 그 가드는 공용 패턴 전체에 적용되지, 마지막 패턴에만 적용되는 게 아니에요. 예를 들어 아래 코드에서 가드 when a > 41은 A a와 B a 양쪽에 모두 적용돼요.
type Union =
| A of int
| B of int
let foo() =
let test = A 40
match test with
| A a
| B a when a > 41 -> a // the guard applies to both patterns
| _ -> 1
foo() // returns 1
test는 A 40이니 A a에 맞긴 하는데 가드가 a > 41이라서(40은 41보다 크지 않죠) 이 패턴엔 걸리지 않아요. B a도 가드가 같으니 마찬가지고요. 결국 _에 걸려서 이 예시는 1을 돌려줘요.