Functional Programming in Swift(4)
Chapter 5 Optionals
이 문서는'Functional Programming in Swift'의 5장 노트입니다. 관심이 있으면 영문 원본을 구매하세요.
1. Case Study: Dictionaries
swift에서 dictionary와array 대상은 모두 범형을 통해 이루어진다.다음 도시의 디렉터리 대상을 만듭니다
let cities = ["Paris": 2243, "Madrid": 3216, "Amsterdam": 881, "Berlin": 3397]
이 도시 사전의 유형은 [string: Int]입니다. 키를 사용하여 그의value를 인덱스할 수 있습니다.
let madridPopulation: Int = cities["Madrid"]
그러나 이렇게 유형 검사를 하지 않으면 키에 대응하는value가 사전에 존재하지 않을 가능성이 높기 때문에 옵션al 형식을 사용할 수 있습니다
let madridPopulation: Int? = cities["Madrid"]
물론 사용하기 전에, 우리는 선택할 수 있는 귀속을 사용하여 가방을 해제해야 한다
if let madridPopulation = cities["Madrid"] {
println("The population of Madrid is " + "\(madridPopulation * 1000)")
} else {
println("Unknown city: Madrid")
}
일반적으로 이런 선택할 수 있는 귀속 방법으로 강제 하도급을 해제하는 것을 추천한다. 이런 강제 하도급은 우리로 하여금 자발적으로 이상 처리를 하게 할 수 있다. 과일 공식 문서에서 언급한 implicitly unwrapped optionals서에 의하면 이것은 abad code smell이라고 한다. 그러나 실제 공사에서 IBOutlet 등 속성은 이런 은식 하도급을 사용하는 것이 매우 유용하다.
swift는 또한 안전한 사용을 제공했다!의 방식은 nil이 될 때 기본값을 제공해야 한다는 것이다. 대략적으로 말하자면 다음과 같다.
infix operator ??
func ??(optional: T?, defaultValue: T) -> T {
if let x = optional {
return x
} else {
return defaultValue
}
}
이것은 swift의 조작부호입니다.??왼쪽의 선택할 수 있는 형식의 패키지 해제에 성공하면 패키지 해제 후의 값을 출력하고, 그렇지 않으면 오른쪽의 기본값을 출력합니다.
그러나 여기에 문제가 하나 있다. swift라는 언어는 C의 예처리를 폐지했다. 그의 함수는 매개 변수를 전달할 때 매개 변수에 대해 값을 구한다. 특히 매개 변수가 표현식일 때이다.만약defaultValue가 대량의 계산을 필요로 하는 표현식이라면 이런 방식을 사용하면 옵션al이 가방을 뜯는 결과가 어떻든지 간에 전참하는 과정에서defaultValue는 값을 구한다. 이것은 우리가 원하는 것이 아니다. 왜냐하면 우리가 기대하는 결과는 그가 가방을 뜯는 데 실패할 때만 다시 계산하기 때문이다.
해결 방법도 간단합니다. 우리는defaultValue의 유형을 변경할 수 있습니다. () -> T. 이것은 클립이 되고 매개 변수가void인 경우에만 실행됩니다.
func ??(optional: T?, defaultValue: () -> T) -> T {
if let x = optional {
return x
} else {
return defaultValue()
}
}
이런 식으로 정의된 조작부호는 당연히 호출할 때 클립을 만들어야 한다.
myOptional ?? { myDefaultValue }
매번 수동으로 클립을 만드는 것을 피하기 위해 swift 기본 라이브러리는 더 좋은 해결 방안인 autoclosure type attribute를 제공합니다. 이 동동은 사실 클립을 만들어야 하는 모든 상황을 은밀하게 봉인합니다. 다시 말하면 자동으로 클립을 만들 것입니다. @autoclosure로 다시 실현합니까?:
infix operator ?? { associativity right precedence 110 }
func ??(optional: T?, defaultValue: @autoclosure () -> T) -> T {
if let x = optional {
return x
} else {
return defaultValue()
}
}
2. CombiningOptionalValues
Optional Chaining
swift의optionalvalues는 마치 블랙박스와 같다. 그를 열기 전에 그 안에 값이 있는지 없는지는 아무도 모른다. 여러 optionalvalue에 의존 관계가 존재하면 처리하기가 비교적 복잡하다.
struct Order {
let orderNumber: Int
let person: Person? // ...
}
struct Person {
let name: String
let address: Address? // ...
}
struct Address {
let streetName: String let city: String
let state: String?
// ...
}
주문서를 드리겠습니다. 소비자의 주 주소 (state) 를 어떻게 찾아서 은밀한 하도급 해제 방안을 사용하는지.
order.person!.address!.state!
그러나 데이터가 부족하면 실행 중 오류가 발생할 수 있습니다. 다음에 선택할 수 있는 귀속을 사용하면 안전할 수도 있습니다.
if let myPerson = order.person in {
if let myAddress = myPerson.address in {
if let myState = myAddress.state in {
// ...
하지만 너무 수다스러워요. 아니요, 선택할 수 있는 체인을 사용하는 게 좋을 수도 있어요(optional chaining)
if let myState = order.person?.address?.state? {
print("This order will be shipped to \(myState\)")
} else {
print("Unknown person, address, or state.")
}
Maps and More
조작부호옵션으로 사용할 수 있는 메서드와 등록 정보를 선택할 수 있으며 함수를 사용하여 옵션 값을 조작할 수 있는 예도 있습니다.
func incrementOptional(optional: Int?) -> Int? {
if let x = optional {
return x + 1
} else {
return nil
}
}
incrementOptional의 행위는 조작부호와 유사합니다.선택할 수 있는 값을 해제하지 못하여nil로 되돌아오면 계산을 실행합니다.우리는 incrementOptional과?범용화를 결합하여 맵 함수를 정의합니다.
func map(optional: T?, f: T -> U) -> U? {
if let x = optional {
return f(x)
} else {
return nil
}
}
map 함수에는 두 개의 매개 변수가 있습니다: 선택할 수 있는 유형 T?,옵션 T인 경우 함수 유형 T -> U패키지 해제에 성공해야 계산을 할 수 있습니다. 그렇지 않으면 nil로 돌아갑니다.map 함수도 사실 Swift 표준 라이브러리의 일부분입니다.맵을 사용하여 incrementOptional 함수를 다음과 같이 씁니다.
func incrementOptional2(optional: Int?) -> Int? {
return optional.map { x in x + 1 }
}
우리도 우리 프로젝트의 옵션al struct와 옵션al class에 맵 방법을 적용할 수 있다.왜 이 함수를 맵이라고 부르는가. 그는 수조의 맵과 어떤 관계가 있는지, 이 문제는 14장에 놓고 다시 토론한다.)
Optional Binding Revisited
map 방법은 선택할 수 있는 값을 조작하는 방식을 보여 주지만 다른 방법도 있다. 아래의 예를 생각해 보자.
let x: Int? = 3
let y: Int? = nil
let z: Int? = x + y
컴파일러가 오류를 보고할 것입니다. 왜냐하면 덧셈 작업은 선택할 수 없는 값 위에만 사용할 수 있기 때문입니다.이 문제를 해결하기 위해 우리는 if문장을 끼워넣는 방식을 소개한 적이 있다.
func addOptionals(optionalX: Int?, optionalY: Int?) -> Int? {
if let x = optionalX {
if let y = optionalY {
return x + y
}
}
return nil
}
다시 한 번 예를 들어 우리는 사전(이전 장의 예를 참조)이 하나 있는데 각 국가와 대응하는 수도를 포함한다. 만약에 한 나라를 지정하면 대응하는 수도의 인구로 돌아가야 한다. 우리는 사전을 두루 훑어보고 아래의 함수를 집행해야 한다. 모든 국가는 함수 안에 겹겹이 끼워 넣어야 한다.
func populationOfCapital(country: String) -> Int? {
if let capital = capitals[country] {
if let population = cities[capital] {
return population * 1000
}
}
return nil
}
이렇게 겹겹이 끼워 넣는 if는 정말 재미없다. 다행히도 Swfit라는 언어는 일등 공민이다. 우리는 하나의 조작부호를 사용자 정의하여 끼워 넣는 문제를 해결할 수 있다.
infix operator >>= {}
func >>=(optional: T?, f: T -> U?) -> U? {
if let x = optional {
return f(x)
} else {
return nil
}
}
조작부호 > = 선택할 수 있는 값을 판정합니다. 만약 nil이 아니라면, 함수 f에 매개 변수로 전달하고, 선택할 수 있는 값이 nil이면 최종 결과는 nil로 되돌아옵니다.이 조작부호를 사용하면 위의 예를 다시 쓸 수 있습니다.
func addOptionals2(optionalX: Int?, optionalY: Int?) -> Int? {
return optionalX >>= { x in
optionalY >>= { y in x+y
}
}
}
func populationOfCapital2(country: String) -> Int? {
return capitals[country] >>= { capital in
cities[capital] >>= { population in
return population * 1000
}
}
}
3. WhyOptionals?
Objective-C에 익숙한 사람에게는 선택할 수 있는 유형이 이상하다고 생각하지만, Swift 유형 시스템은 상당히 정확할 것이다. 옵션 유형을 사용하면 nil을 처리해야 한다는 것을 의미한다.OC에서 위의 예를 쓰는 것은 상당히 편리할 것이다. 비공식 유형을 미리 판단할 필요가 없고 컴파일러도 경고를 주지 않는다.
- (int)populationOfCapital:(NSString *)country {
return [self.cities[self.capitals[country]] intValue] * 1000;
}
물론 중간 어느 단계가nil이면 최종 결과는 0.0이고 모든 것이 좋다.선택할 수 없는 많은 언어에서 빈 바늘은 모든 위험의 근원이다.그러나 OC에서는 이 모든 것이 적고, 안전하게 nil에 메시지를 보낼 수 있으며, 되돌아오는 유형에 따라 nil, 0, 0과 비슷한 값을 얻을 수 있다.그런데 왜 swift에서 이 모든 것을 바꿔야 합니까?
선택할 수 있는 유형을 명확하게 선택하는 이유 중 하나는 언어의 정적 안전성을 높이는 것이다. 강력한 유형 시스템은 코드가 실제로 실행되기 전에 오류를 잡을 수 있고, 선택할 수 있는 유형은 어떤 값이 부족해서 예측할 수 없는 붕괴를 방지할 수 있다.
OC와 같은 언어는 그에게도 단점이 있다. 만약에 한 사전의 키를 사용하여 nil로 되돌아온다면 이때 키가 nil로 되돌아오지 않는지, 키가 사전과 존재하는지 판단해야 한다. 그러나 대응하는 값이 nil이라는 점을 구분하기 위해 NSNull만 사용할 수 있다.
OC에서 니엘에게 안전하게 메시지를 보낼 수 있지만 안전하게 사용할 수 있다는 것은 아니다.전달된country 매개 변수를 nil로 설정하면 자본도 nil이지만 NSAttributed String이 초기화될 때 이nil을 사용하는country 매개 변수를 사용하면 붕괴됩니다.
- (NSAttributedString *)attributedCapital:(NSString *)country {
NSString *capital = self.capitals[country];
NSDictionary *attributes = @{ /* ... */ };
return [[NSAttributedString alloc] initWithString:capital attributes:attributes];
}
이런 Crash는 빈번하지는 않지만 많은 개발자의 프로그램에서 이런 Crash가 나타나고 debug에 걸리는 시간도 적지 않다. 똑똑한 프로그래머들은 보통 asserts를 사용하여 검증한다.
- (NSAttributedString *)attributedCapital:(NSString *)country {
NSParameterAssert(country);
NSString *capital = self.capitals[country];
NSDictionary *attributes = @{ /* ... */ };
return [[NSAttributedString alloc] initWithString:capital attributes:attributes];
}
위에서 우리가 전송한country가nil이면, 즉시 fails가 디버깅을 편리하게 할 것이라고 예언합니다.그러나 우리가 전달한 contry와capitals 사전이 일치하지 않으면 어떻게capitals 사전에 키가 없으면 문자열capital는nil이고, 마찬가지로 최종 프로그램은 붕괴됩니다.물론 이런 오류를 고치는 것도 간단하지만, 우리가 말하고자 하는 것은 스와프를 사용하여 이런 nil이 일으키는 Crash를 처리하는 것이 훨씬 간단하고, 스와프를 사용해도 건장성 코드를 쓰기 쉽다는 것이다.
마지막으로 이러한 assertions를 사용하는 것은 천연 비모듈화입니다. 만약에 우리가 checkCountry 방법을 실현하여 이러한 비공식 문자열이 존재하는지 확인합니다.
- (NSAttributedString *)attributedCapital:(NSString*)country {
NSParameterAssert(country);
if (checkCountry(country)) {
// ...
}
}
이제 의문이 또 왔다. checkCountry 함수 내부의 실현도 하나의 assert가 인자가 nil인지 아닌지를 판단해야 하는가?한편으로는 필요없다. 우리는 이미attributed Capital 방법에서 검사했다.다른 한편, 만약 checkCountry 함수가 비nil 인자만 지원한다면, 우리는 assertion을 그의 내부로 복사해야 한다.두 가지 모순된 결론을 종합해 보면 우리는 안전하지 않은 인터페이스를 사용하거나 assertion을 하나 더 복제하는 두 가지 선택에 직면해 있다.
Swift에서 모든 것이 간단해졌습니다. 선택할 수 있는 유형은 이 값이 nil일 수도 있다는 것을 명확히 하는 것입니다. 이것은 상당히 귀중한 정보입니다. 아래의 함수 설명과 같습니다.
func attributedCapital(country: String) -> NSAttributedString?
이 함수 이름에서 우리는 반환 값이 실패할 수도 있다는 것을 명확하게 알 수 있을 뿐만 아니라, 전송된 매개 변수country가 반드시 nil이 아니라는 것을 알 수 있을 뿐만 아니라, 컴파일러 역시 이러한 정보를 이용할 수 있다.
또 다른 경우, 만약에 우리가 표량을 처리할 때 선택할 수 있는 유형이 OC에서 더욱 교활해진다. 예를 들어, 우리는 하나의 문자열에서 특정한 키워드를 찾으려고 시도한다. 정상적인 상황에서rangeOfString: 일치하는 문자열을 찾지 못하면 결과는 NSNotFound로 돌아가야 하고, NSNotFound의 정의는 -1이다.이 코드는 거의 항상 정상적으로 작동하지만, 은근한 버그가 있고 검색하기 어렵다. range OfString: 메시지의 수신자가 nil이면 0으로 가득 찬 구조체로 되돌아오고location은 0으로 설정된다. 0은 NSNotFound 대표의 -1과 같지 않기 때문에 최종 결과는 시종일관 성립될 것이다.
NSString *someString = ...;
//// someString nil rangeOfString range = location=0,
if ([someString rangeOfString:@"swift"].location != NSNotFound]) {
NSLog(@"Someone mentioned swift!");
}
그러나 선택할 수 있는 유형을 사용하면 이 모든 것이 일어나지 않는다.상기 코드는 Swift에서 컴파일러에 의해 직접 거부될 것입니다. 왜냐하면 형식 시스템은range OfString을 허용하지 않기 때문입니다. 방법은 선택할 수 있는 형식 위에서 실행되며, 패키지를 해제해야 이 방법을 사용할 수 있습니다.
if let someString = ... {
if someString.rangeOfString("swift").location != NSNotFound {
println("Found")
}
}
유형 시스템과 컴파일러는 개발 단계에서 오류를 줄이고 더 건장한 코드를 작성하는 데 도움을 줄 것이다.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
다양한 언어의 JSONJSON은 Javascript 표기법을 사용하여 데이터 구조를 레이아웃하는 데이터 형식입니다. 그러나 Javascript가 코드에서 이러한 구조를 나타낼 수 있는 유일한 언어는 아닙니다. 저는 일반적으로 '객체'{}...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.