Apple 에서 공개한 The Swift Programming Language (Swift 5.7) 책의 Patterns 부분1을 번역하고, 설명이 필요한 부분은 주석을 달아서 정리한 글입니다. 전체 번역은 Swift 5.7: Swift Programming Language (스위프트 프로그래밍 언어) 에서 확인할 수 있습니다.
패턴 (pattern) 은 단일 값 또는 합성 값의 구조를 나타냅니다. 예를 들어, 튜플 (1, 2)
의 구조는 쉼표로-구분한 두 원소의 목록입니다. 패턴은 어떤 특별한 값 보단 값의 구조를 나타내기 때문에, 다양한 값과 맞춰볼 수 있습니다. 구체적인 사례로, (x, y)
라는 패턴은 튜플 (1, 2)
및 어떤 다른 두-원소의 튜플과도 맞습니다. 패턴을 값과 맞춰보는 것에 더해, 합성 값의 일부 또는 전체를 뽑아내서 각 부분을 상수나 변수 이름에 연결할 수도 있습니다.
스위프트에는, 두 종류의 기본 패턴이 있는데: 어떤 종류의 값이든 성공적으로 맞춰보는 것과, 지정한 값과 맞춰보는게 실행 시간에 실패할 수도 있는 것이 그것입니다.
첫 번째 종류의 패턴은 단순 변수와, 상수, 및 옵셔널 연결 안의 값을 해체하는 (destructuring) 데 사용합니다. 이는 와일드카드 패턴과, 식별자 패턴, 및 이들을 담은 어떤 값 연결이나 튜플 패턴이든 포함합니다. 이러한 패턴에 타입 보조 설명을 지정하면 특정 타입 값하고만 맞춰보도록 이들을 구속할 수 있습니다.
두 번째 종류의 패턴은 전체 패턴 맞춤 (full pattern matching) 에 사용하는데, 여기선 맞춰보려는 값이 실행 시간에 없을 수도 있습니다. 이는 열거체 case 패턴과, 옵셔널 패턴, 표현식 패턴, 및 타입-변환 패턴을 포함합니다. 이러한 패턴은 switch
문의 case
이름표나, do
문의 catch
절, 또는 if
나, while
, guard
, 또는 for
-in
문의 조건에 사용합니다.
GRAMMAR OF A PATTERN 부분 생략 - 링크
와일드카드 패턴 (wildcard pattern) 은 어떤 값과도 맞은 다음 (이를) 무시하며 밑줄 (_
) 로 구성합니다. 맞춰볼을 값에 신경 쓰지 않을 때 와일드카드 패턴을 사용합니다. 예를 들어, 다음 코드는 닫힌 범위 1...3
을 반복하면서, 반복문 각 회차의 현재 범위 값을 무시합니다:
for _ in 1...3 {
// 어떤 걸 세 번 반복합니다.
}
GRAMMAR OF A WILD PATTERN 부분 생략 - 링크
식별자 패턴 (identifer pattern) 은 어떤 값과도 맞으며 그 맞은 값을 변수나 상수 이름에 연결합니다. 예를 들어, 다음 상수 선언의, someValue
라는 식별자 패턴은 Int
타입 값 42
와 맞습니다:
let someValue = 42
맞춤이 성공할 땐, 42
값을 someValue
라는 상수 이름에 연결 (할당) 합니다.
변수나 상수 선언의 왼쪽 패턴이 식별자 패턴일 땐, 식별자 패턴은 암시적으로 값-연결 패턴의 하위 패턴입니다.
GRAMMAR OF A IDENTIFIER PATTERN 부분 생략 - 링크
값-연결 패턴 (value-binding pattern) 은 맞은 값을 변수나 상수 이름에 연결합니다. 맞은 값을 상수 이름에 연결하는 값-연결 패턴은 let
키워드로 시작하며; 변수 이름에 연결하는 건 var
키워드로 시작합니다.
값-연결 패턴 안에 있는 식별자 패턴은 새로 이름의 변수나 상수를 자신과 맞는 값에 연결합니다. 예를 들어, 튜플 원소를 분해하여 각 원소 값을 해당하는 식별자 패턴에 연결할 수 있습니다.
let point = (3, 2)
switch point {
// x 와 y 를 point 원소에 연결함.
case let (x, y):
print("The point is at (\(x), \(y)).")
}
// "The point is at (3, 2)." 를 인쇄함
위 예제에선, let
을 (x, y)
튜플 패턴의 각 식별자 패턴으로 배포합니다.2 이런 동작 때문에, case let (x, y):
와 case (let x, let y):
라는 switch
문 case 에 맞는 값은 똑같습니다.
GRAMMAR OF A VALUE-BINDING PATTERN 부분 생략 - 링크
튜플 패턴 (tuple pattern) 은 쉼표로-구분한 0 개 이상의 패턴 목록을, 괄호로 테두리 친 것입니다. 튜플 패턴은 해당하는 튜플 타입의 값과 맞춰봅니다.
튜플 타입을 구속하여 특정 종류의 튜플 타입과 맞춰보려면 타입 보조 설명을 사용하면 됩니다. 예를 들어, 상수 선언 let (x, y): (Int, Int) = (1, 2)
안의 (x, y): (Int, Int)
라는 튜플 패턴은 그 안의 원소가 둘 다 Int
일 때만 맞춰봅니다.
튜플 패턴을 for
-in
문 안의 패턴이나 변수 또는 상수 선언 안의 패턴으로 사용할 땐, 와일드카드 패턴이나, 식별자 패턴, 옵셔널 패턴, 또는 이를 담은 다른 튜플 패턴만을 담을 수 있습니다. 예를 들어, 다음 코드는 유효하지 않은데 튜플 패턴 (x, 0)
안의 원소 0
이 표현식 패턴이기 때문입니다:3
let points = [(0, 0), (1, 0), (1, 1), (2, 0), (2, 1)]
// 이 코드는 유효하지 않습니다.
for (x, 0) in points {
/* ... */
}
단일 원소만 담은 튜플 패턴 주변의 괄호는 아무런 효과가 없습니다. 패턴은 그 단일 원소 타입인 값과 맞춰봅니다. 예를 들어, 다음은 서로 같은 겁니다:
let a = 2 // a: Int = 2
let (a) = 2 // a: Int = 2
let (a): Int = 2 // a: Int = 2
GRAMMAR OF A TUPLE PATTERN 부분 생략 - 링크
열거체 case 패턴 (enumeration case pattern) 은 기존 열거체 타입의 case 와 맞춰봅니다. 열거체 case 패턴은 switch
문의 case 이름표 안에서 그리고 if
와, while
, guard
, 및 for
-in
문의 조건 안에서 나타납니다.
맞춰보려는 열거체 case 값에 어떤 결합 값이 있으면, 해당하는 열거체 case 패턴에도 반드시 각각의 결합 값마다 한 원소씩 담은 튜플 패턴을 지정해야 합니다. switch
문을 써서 결합 값을 담은 열거체 case 와 맞춰보는 예제는, Associated Values (결합 값) 부분을 보기 바랍니다.
열거체 case 패턴은 그 case 를 옵셔널 안에 포장한 값과도 맞춰봅니다. 이 단순해진 구문은 옵셔널 패턴을 생략하게 합니다. Optional
은 열거체로 구현하기 때문에, 똑같은 swift 문 안에 .none
과 .some
이 열거체 타입의 case 로 나타날 수 있다는 걸 기억하기 바랍니다.
enum SomeEnum { case left, right }
let x: SomeEnum? = .left
switch x {
case .left:
print("Turn left")
case .right:
print("Turn right")
case nil:
print("Keep going straight")
}
// "Turn left" 를 인쇄함
GRAMMAR OF AN ENUMERATION CASE PATTERN 부분 생략 - 링크
옵셔널 패턴 (optional pattern) 은 Optional<Wrapped>
열거체의 some(Wrapped)
case 안에 포장한 값과 맞춰봅니다. 옵셔널 패턴은 식별자 패턴과 그 바로 뒤의 물음표로 구성하며 열거체 case 패턴과 동일한 곳에 나타납니다.
옵셔널 패턴은 Optional
열거체 case 패턴의 수월한 구문이기 때문에, 다음은 서로 같은 겁니다:
let someOptional: Int? = 42
// 열거체 case 패턴으로 맞춰봅니다.
if case .some(let x) = someOptional {
print(x)
}
// 옵셔널 패턴으로 맞춰봅니다.
if case let x? = someOptional {
print(x)
}
옵셔널 패턴은 편리한 방법을 제공하여 for
-in
문의 옵셔널 값 배열을 반복하는데, nil
-아닌 원소에만 반복문 본문을 실행합니다:
let arrayOfOptionalInts: [Int?] = [nil, 2, 3, nil, 5]
// nil-아닌 값만 맞춰봅니다.
for case let number? in arrayOfOptionalInts {
print("Found a \(number)")
}
// Found a 2
// Found a 3
// Found a 5
GRAMMAR OF AN ENUMERATION CASE PATTERN 부분 생략 - 링크
두 개의 타입 변환 패턴이 있는데, is
패턴과 as
패턴입니다. is
패턴은 switch
문의 case 이름표에만 나타납니다. is
와 as
패턴의 형식은 다음과 같습니다:
is type-타입
pattern-패턴
as type-타입
is
패턴은 실행 시간에 그 값의 타입이 is
패턴 오른쪽에 지정한 타입-또는 그 타입의 하위 클래스-와 똑같으면 맞춰봅니다. is
패턴은 둘 다 타입 변환을 한다는 건 is
연산자 같이 동작하지만 반환 타입을 버립니다.
as
패턴은 실행 시간에 그 값의 타입이 as
패턴 오른쪽에 지정한 타입-또는 그 타입의 하위 클래스-와 똑같으면 맞춰봅니다. 맞춤이 성공하면, 맞은 값의 타입을 as
패턴 오른쪽에서 지정한 패턴 (pattern) 으로 변환합니다.
switch
문을 써서 is
와 as
패턴으로 값을 맞추는 예제는, Type Casting for Any and AnyObject (Any 와 AnyObject 의 타입 변환) 부분을 보기 바랍니다.
GRAMMAR OF A TYPE CASTING PATTERN 부분 생략 - 링크
표현식 패턴 (expression pattern) 은 표현식의 값을 나타냅니다. 표현식 패턴은 switch
문의 case 이름표 안에서만 나타납니다.
표현식 패턴이 나타내는 표현식과 입력 표현식 값의 비교는 스위프트 표준 라이브러리의 ~=
연산자4 로 합니다. ~=
연산자가 true
를 반환하면 맞춤이 성공합니다. 기본적으로, ~=
연산자는 ==
연산자로 동일한 타입의 두 값을 비교합니다. 아래 예제에서 보는 것처럼, 범위 안에 값이 담겼는지를 검사하여, 값을 값의 범위와도 맞춰볼 수 있습니다.
let point = (1, 2)
switch point {
case (0, 0):
print("(0, 0) is at the origin.")
case (-2...2, -2...2):
print("(\(point.0), \(point.1)) is near the origin.")
default:
print("The point is at (\(point.0), \(point.1)).")
}
// "(1, 2) is near the origin." 를 인쇄함
~=
연산자를 중복 정의하여 자신만의 표현식 맞춤 동작을 제공할 수도 있습니다. 예를 들어, 위의 예제를 재작성하여 point
표현식과 점의 문자열 표현법을 비교할 수 있습니다.
// ~= 연산자 중복 정의하여 문자열과 정수를 맞춰봅니다.
func ~= (pattern: String, value: Int) -> Bool {
return pattern == "\(value)"
}
switch point {
case ("0", "0"):
print("(0, 0) is at the origin.")
default:
print("The point is at (\(point.0), \(point.1)).")
}
// "The point is at (1, 2)." 를 인쇄함
GRAMMAR OF AN EXPRESSION PATTERN 부분 생략 - 링크
Generic Parameters and Arguments (일반화 매개 변수와 인자) >
‘let
을 각 식별자 패턴으로 배포한다’ 는 건, 본문 뒤의 설명 처럼, let (x, y)
를 자동으로 (let x, let y)
로 만들어 준다는 의미입니다. ↩
스위프트 프로그래밍에서 유효하다 (valid) 또는 무효하다 (invalid) 라는 말이 나오는데, 기준은 컴파일이 되느냐 안되느냐의 차이입니다. 즉, 본문 예제가 유효하지 않는다는 건 컴파일이 되지 않는 코드라는 의미입니다. ↩
Zafar Ivaev 의 What is the ~= Operator in Swift? 항목을 참고하면, 스위프트 표준 라이브러리의 ~=
연산자는 범위 (range) 안의 각 값을 ==
연산자로 비교하는 것이라고 합니다. 즉, 특정한 값이 그 범위 안에 있는지 검사하는, 일종의 ‘범위 비교 연산자’ 라고 할 수 있습니다. ↩