
자주 사용하는 고차함수들에 대해서 이해해보자.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]