[Swift] Optional 에서 map vs flatMap

closure 가 optional을 return하는 타입일때 map과 flatMap의 차이 발생

naljin
5 min readSep 29, 2021

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
}
}

maptransform(y) 의 값을 optional로 감싸서 return ( .some(try transform(y))) 한다

이걸 바탕으로 간단한 예시를 보자

let number: Int? = 1
let res1 = number.map { $0 + 1 }
print(res1) // Optional(2)

numbernil 이 아니므로 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
}
}

flatMapmap 과 달리 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) }

numbernil 이 아니므로 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? 이 된다.

출처

--

--