[Swift] Optional 에서 map vs flatMap
map 분석
우선 Optional
에서 map
의 구현체를 보자
@inlinable
public func map<U>(
_ transform: (Wrapped) throws -> U
) rethrows -> U? {
switch self {
case .some(let y):
return .some(try transform(y))
case .none:
return .none
}
}
map
은 transform(y)
의 값을 optional로 감싸서 return ( .some(try transform(y))
) 한다
이걸 바탕으로 간단한 예시를 보자
let number: Int? = 1
let res1 = number.map { $0 + 1 }
print(res1) // Optional(2)
number
가 nil
이 아니므로 number
값인 1
이 unwrap 되어 closure 로 pass 된다 (transform(y)
=> transform(1)
)
transform
에 해당하는 { $0 + 1 }
클로저는 +1
을 더해 ({1 + 1}
) 2
(U
) 를 리턴한다
위의 값을 optional로 감싼 형태, 즉 .some(2)
를 Optional(2)
(U?
) 로서 최종 리턴 한다
flatMap 분석
그 다음 flapMap
의 구현체를 보자
@inlinable
public func flatMap<U>(
_ transform: (Wrapped) throws -> U?
) rethrows -> U? {
switch self {
case .some(let y):
return try transform(y)
case .none:
return .none
}
}
flatMap
은 map
과 달리 transform(y)
의 값을 optional 로 감싸지 않고 그대로 return 한다. ( try transform(y)
)
이걸 바탕으로 간단한 예시를 보자
let number: Int? = 1
let res2 = number.flatMap { $0 + 1 }
print(res2) // Optional(2)
여기서 flatMap
은 (Wrapped) -> U?
형태의 optional 을 return 하는 클로저를 받길 기대하는데 { $0 + 1 }
은 optional 을 return 하지 않는다.
이렇게 flatMap의 closure가 optional을 return 하지 않는다면 컴파일러가 자동으로 클로저의 return type 을 optional 로 변환한다.
let res2 = number.flatMap { return Optional($0 + 1) }
number
가 nil
이 아니므로 1
값이 unwrap 되어 closure 로 pass 되고, 클로저는 +1
을 더해 Optional(2)
(U?
) 를 리턴한다.
그리고 이 값이 그대로 flatMap
의 return value 가 된다.
차이
위의 예시는 map
이나 flatMap
이나 결과적으로는 optional 을 반환하는게 비슷해 보인다. 그런데 만약 transform closure 가 optional을 return 하는 타입이라면 어떻게 될까?
flatMap()
은 transform(y)
을 그대로 return 하니까 closure 결과값으로 생긴 optional 값을 그대로 리턴한다.
반면 map()
은 transform(y)
을 optional로 감싸서 .some(try transform(y))
형식으로 return 하니까 closure 결과 값으로 생긴 optional 값을 한번 더 optional 로 감싸서 return한다.
func createURL(_ string: String) -> URL? {
return URL(string: string)
}
let s: String? = "nalin"
let u1 = s.map { createURL($0) }
let u2 = s.flatMap { createURL($0) }
print(type(of: u1)) //Optional<Optional<URL>>
print(type(of: u2)) //Optional<URL>
왜 이런 결과가 나오는지 시그니처를 통해 한번 더 살펴 보자
map
의 시그니처는 이렇게 생겼다.
func map<U>((Wrapped) -> U) -> U?
위의 예시에서 클로저로 들어가는 createURL
은 (String) -> URL?
타입이다
이를 (Wrapped) -> U
에 대입해보면 U
의 타입은 URL?
이기 때문에, 결과적으로 return 하는 U?
타입은 URL??
이 된다.
반면 flatMap
의 시그니처는 아래와 같다
func flatMap<U>((Wrapped) -> U?) -> U?
마찬가지로 클로저로 들어가는 createURL
은 (String) -> URL?
타입이다
이를 (Wrapped) -> U?
에 대입해보면 U
의 타입은 URL
이기 때문에, 결과적으로 return 하는 U?
타입은 URL?
이 된다.