Swift - where 절
스위프트의 where 절은 특정 패턴과 결합하여 조건을 추가하는 역할을 한다. where 절은 크게 두 가지 용도로 사용된다.
- 패턴과 결합하여 조건 추가
- 타입에 대한 제약 추가
즉 특정 패턴에 Bool 타입 조건을 지정하거나, 어떤 타입의 특정 프토코로 준수 조건을 추가하는 등의 기능이 있다. 예제를 통해서 알아보도록 하자
값 바인딩, 와일드카드 패턴과 결합한 where 절
let tuples : [(Int,Int)] = [(1,2),(1,-1),(1,0),(0,2)]
for tuple in tuples {
switch tuple {
//값 바인딩
case let (x,y) where x==y : print("x==y")
case let (x,y) where x == -y: print("x==-y")
case let (x,y) where x>y:print("x>y")
//와일드카드 패턴
case (1,_): print("x==1")
case (_,2): print("y==2")
default: print("\(tuple.0), \(tuple.1)")
}
}
값 바인딩, 또는 와일드 카드 패턴을 사용한 where 절로 조건을 추가할 수 있다. 튜플의 데이터를 x 와 y 상수로 바인딩하고, where 절을 사용해 x 와 y 를 비교하는 것을 볼 수 있다. where 절 뒤에는 Bool 타입의 표현식이 오면 된다. where 절을 사용해 값을 바인딩하고도 추가적인 조건을 통해 switch case 문에 접근할 수 있다. 만약 where 절이 없다면 코드는 다음과 같이 작성할 수 있을 것이다.
for tuple in tuples {
switch tuple {
case let (x,y):
if x == y {
print("x==y")
} else if x == -y {
print("x==-y")
} else if x>y {
print("x>y")
}
}
}
딱 그냥 보기에도 코드가 좀 더 복잡해진 것을 볼 수 있다. 이처럼 where 절을 사용하면 효과적으로 코드를 줄이고 가독성을 늘릴 수 있다.
타입 캐스팅과 where
where 절을 타입캐스팅 패턴과 결합할 수 있다.
let anyValue : Any = "ABC"
switch anyValue {
//value 로 값을 바인딩하고, 타입 검사
case let value where value is String: print("value is String")
case let value where value is Int: print("value is Int")
case let value where value is Float :print("value is Float")
default: print("value is unknown type")
}
var things : [Any] = [0,0.0,"321",42,(3.0,5.0)]
for any in things {
switch any {
//값을 바인딩하고, 타입 검사 및 캐스팅
case let someInt where someInt is Int : print("\(someInt) is Int")
case let someDouble where someDouble is Double : print("\(someDouble) is Double")
case let someString where someString is String : print("\(someString) is String")
case let (double1,double2) as (Double,Double) : print("value is tuple")
default: print("\(any) is unknown type")
}
}
//출력 결과
value is String
0 is Int
0.0 is Double
321 is String
42 is Int
value is tuple
또 where 절은 표현 패턴과도 결합할 수 있다.
var point : (Int, Int) = (1,2)
switch point {
case (0,0) : print("원점")
case (-2...2,-2...2) where point.0 != 1 : print("\(point) 는 원점과 가깝다.")
default : print("\(point)")
}
//출력 결과
(1,2)
Protocol Extension 와 where
프로토콜 익스텐션에 where 절을 사용하면 해당 익스텐션이 특정 프로토콜을 준수하는 타입에만 적용될 수 있도록 제약을 줄 수 있다. 다시 말해 익스텐션이 적용된 프로토콜을 준수하는 타입 중 where 절 뒤에 제시되는 프로토콜도 준수하는 타입만 익스텐션이 적용되도록 제약을 줄 수 있다.
protocol ToString {
func toString() -> String
}
extension Int : ToString {
}
extension UInt: ToString {
}
extension Double : ToString {
}
extension ToString where Self : FixedWidthInteger,Self : SignedInteger {
func toString() -> String {
"FixedWidthInger, SignedInteger 프로토콜 준수 타입 \(type(of:self))"
}
}
extension ToString {
func toString() -> String {
"그 외 타입! \(self)"
}
}
let intValue = 5
let intValue2 = 10
let uInt : UInt = 291239012
let doubleValue = 29.0123
print(intValue.toString()) //FixedWidthInteger, SignedInteger 프로토콜 준수 타입 Int
print(intValue2.toString()) //FixedWidthInteger, SignedInteger 프로토콜 준수 타입 Int
print(uInt.toString()) //그 외 타입! 291239012
print(doubleValue.toString()) //그 외 타입! 29.0123
Int, UInt, Double 타입에 toString() 메소드를 추가하기 위해 ToString 이란 프로토콜을 생성하고, 익스텐션으로 각 타입이 ToString 프로토콜을 준수하게 만들었다. 그리고 where 절을 사용해 Int 타입에 적용되는 toString 함수와, 그 외 ToString 프로토콜을 준수하는 모든 타입에 사용되는 toString() 메소드를 익스텐션을 사용해 구현하였다.
타입 매개변수나 연관 타입의 타입 제약 추가
타입 매개변수와 연관 타입의 제약을 추가하는 데 where 절을 사용하기도 한다. 제네릭 함수의 반환 타입 뒤에 where 절을 포함하면 타입 매개변수와 연관 타입에 요구사항을 추가할 수 있다.
func doubled<T>(integerValue : T) -> T where T : BinaryInteger {
return integerValue * 2
}
//위 함수와 동일하다. 따라서 redeclaration 에러가 뜬다
//func doubled<T : BinaryInteger>(integerValue : T) -> T {
// return integerValue * 2
//}
func prints<T,U>(first:T,second:U) where T : CustomStringConvertible, U:CustomStringConvertible {
print(first)
print(second)
}
func printValue<T>(value:T) where T : ToString {
print(value.toString())
}