자주 사용하는 고차함수들에 대해서 이해해보자.map, flatMap과 같은 경우는 여러가지가 존재하는데, 각각의 활용 방법에 대해서 알아보자. 각각의 고차함수의 Documnentation을 모두 달아두었으니, 한번씩 읽어보는 것을 잊지말자.
for-in loop와 유사한 동작
for each in [1, 2, 3] {
if each == 2 {
break // for-in문의 경우 break 가능
}
print(each)
}
// forEach의 경우 break 불가능
[1, 2, 3].forEach { (each: int) in
print(each)
}
어떻게에 집중되어 있는 for in문은, 특정 컬렉션의 값을 변경할 때 로직을 변경해주어야 함
하지만 forEach를 사용하면 동작을 교체하여 적용할 수 있다는 점에서 보다 간결함
Element를 Transform을 이용하여 다른 Type의 Element로 변환
Optional도 Map Function을 가짐
값이 존재할 때 실행
예시
Optional의 값이 있는 경우 map안의 함수를 실행
let tempName: String? = "wansik"
let checker: (String) -> String? = { name in
if name == "wansik" {
return "nice"
}
return nil
}
let result = tempName.map(checker)
print(result) // if "wansik": Optional(Optional("nice")), else: Optional(nil)
tempName
이 Optional인 경우 map 구문이 실행되지 않고 결과가 nil이다.
하지만 Optional이 아니고 wansik이 들어갔을 때, Optional Map의 경우 원래 context에 wrapping하여 결과를 반환하여 Optional이 중첩된다.
만약 wansik이 아닌 다른 값이 들어갔을 때 역시 Optional(nil)
로 반환된다.
이러한 부분이 상당히 불편하다. 이런 불편함은 flatmap을 활용하여 해결이 가능하다.
용어
flatMap
Map + Flatten
Transform의 결과가 Context와 동일한 Type의 Context라면, 결과는 Context가 중첩해서 존재하게 되는데, 이를 하나로 변환시킨 결과를 만듦
Transform의 결과가 Optional Int인 경우, Context 역시 Optional Int라면, 결과는 Optional이 중첩된 상태가 됨
이 떄, Flatmap을 사용하면 flatten된 결과물로 만들어줌
let result = value.map { (num: Int) -> Int? in
if num % 3 == 0 {
return nil
}
return 3
}
// Optional(Optional(3))
let result = value.flatMap { (num: Int) -> Int? in
if num % 3 == 0 {
return nil
}
return 3
}
// Optional(3)
이중 Collection (이차원 배열)의 경우 1차원으로 변환해줌
let numbers = [[1, 2, 3, [4, 5, 6], [7, 8, 9]]
let result = numbers.flatMap { $0 }
// [1, 2, 3, 4, 5, 6, 7, 8, 9]
Dictionary의 경우 각 Element는 tuple로 받음
[Sequence.Element]
Optional인 경우, 그리고 동작의 실패일 경우 nil을 반환받고 싶을 때 사용
let tempName: String? = "wan"
let checker: (String) -> String? = { name in
if name == "wansik" {
return "nice"
}
return nil
}
let result = tempName.flatMap(checker)
print(result) // if "wansik": Optional("nice"), else: nil
네트워크 처리에서 json을 받고, 이를 파싱하는 경우에 자주 사용한다.
let json: [String: String]? = someFunction() // 네트워크 처리를 통해 받아온 json, Optional
let model = json.flatMap(Model.init) // json의 값이 없어도 nil, Model 초기화에 실패해도 nil
// Model init은 failable initializer라고 가정
예외 처리를 깔끔하게 할 수 있음
func defaultDescription(with str: String?) -> String {
guard let string = str,
let integer = Int(string),
integer <= 100 else {
return "invalid value"
}
return "number is \(integer)"
}
func functionalDescription(with str: String?) -> String {
let result = str.flatMap({ Int($0) })
.flatMap({ $0 <= 100 ? $0 : nil})
.map({ "number is \($0)"})
return result ?? "invalid value"
}
print(defaultDescription(with: "300"))
print(functionalDescription(with: "300"))
Monad
Collection 안의 nil을 제거
let numbers: [Int?] = [1, 2, 3, nil, 4, 5, 6, nil, 7, 8, 9]
let result: [Int] = numbers.compactMap { $0 }
print(result) // 1, 2, 3, 4, 5, 6, 7, 8, 9
[Int]
라는 것에 주의Swift 4.1에서 분리된 이유
[T: S?]
상황의 dictionary에서 value가 nil인 element를 제거할 때 사용[T: S]