flatMap과 compactMap의 차이점, 확실하게 짚어봅시다!

이정훈·2024년 1월 31일
0

Swift 파헤치기

목록 보기
8/10
post-thumbnail

해당 포스트는 모나드란 무엇인가 포스트를 읽고 오시면 이해하는데 도움이 됩니다.

Map

시작에 앞서 map 함수에 대해 간단하게 살펴 보고 넘어가자.

Swift Optional 타입에 정의 되어 있는 map 함수의 형태는 아래와 같다.

public func map<U>(_ transform: (Wrapped) throws -> U) rethrows -> U?

Swift Array 타입에 정의 되어 있는 map 함수의 형태는 아래와 같다.

public func map<T>(_ transform: (Element) throws -> T) rethrows -> [T]

Optional 타입과 Array 타입의 공통점을 생각해보면 두 타입 모두 다른 타입의 값을 담을 수 있는 컨테이너 타입이며 두 타입 모두 map 함수를 지원하는 Functor라는 것이다.

Functor에 대한 설명은 모나드란 무엇인가 참고

Array 타입을 기준으로 map 함수의 정의를 다시 살펴보면, map 함수의 진정한 의미는 단순히 내부 요소를 순회할 수 있는 함수가 아니라 Functor로부터 값을 가져와 T 타입을 반환하는 함수(클로저)를 적용한 뒤, 다시 T타입의 Functor에 담아 반환하는 함수이다.

let numbers: [Int] = [1, 2, 3, 4, 5]
let stringNumbers: [String] = numbers.map { String($0) }

print(stringNumbers)    // ["1", "2", "3", "4", "5"]

flatMap

그럼 flatMap은 무슨 함수일까?

잠시 map으로 돌아와서, 불행하게도 map 함수에는 아래와 같은 단점이 존재한다.

let optionalString: String? = "7"
let optionalNumber = optionalString.map { Int($0) }

이때 optionalNumber의 타입은 어떤 타입일지 생각해 보기 위해 Optional 타입에 정의 되어 있는 map 함수를 다시 생각해보자.

public func map<U>(_ transform: (Wrapped) throws -> U) rethrows -> U?

위의 예시에서 사용된 map함수는 U 타입이 이미 Optional<Int> 타입이므로 map 함수의 반환값은 U? 타입인 Optional<Optional<Int>> 타입이 된다.

print(type(of: optionalNumber))    //Optional<Optional<Int>>

이렇듯 map 함수의 정의를 정확히 알지 못하고 사용하는 경우 원치 않은 형태의 값을 반환 받을 수 있으며, 이에 대한 해결책으로 사용할 수 있는 것이 flatMap 함수이다.

Swift Optional 타입에 정의 되어 있는 flatMap 함수의 형태는 다음과 같다.

public func flatMap<U>(_ transform: (Wrapped) throws -> U?) rethrows -> U?

flatMap 함수는 U? 타입을 반환하는 함수(클로저)를 적용 후 다시 U? 타입을 반환하는 함수이다.

위의 예시에서 map 대신 flatMap을 사용한다면 함수(클로저)의 반환값이 Optional<Int>이므로 flatMap의 반환 값 또한 Optional<Int>가 될것이다.

let optionalString: String? = "7"
let optionalNumber = optionalString.flatMap { Int($0) }
print(type(of: optionalNumber))    //Optional<Int>

이처럼 flatMap은 포장된 값의 내부의 포장되어 있는 값을 평평하게(flat) 펼쳐 동등한 위상으로 만들어 주는 기능을 한다. 동등한 위상으로 만들어 주는 기능은 내부 ElementOptional 형태로 감싸진 Array 타입에서도 확인할 수 있다.

Swift 버전 4.1부터 Sequence 타입에 Optional 타입의 Element를 포장한 경우 flatMap 대신 compactMap을 사용한다.

let optionalNumbers: [Int?] = [1, 2, nil, 4, nil, 6]
//let flattenArray = optionalNumbers.flatMap { $0 }
let flattenArray = optionalNumbers.compactMap { $0 }
print(flattenArray)    //[1, 2, 4, 6]

compactMap

그렇다면 flatMapcompactMap은 완전히 동일한 것일까?

두 함수가 같은지 확인 하기 위해 Array 타입에 정의 되어있는 compactMap의 형태를 확인해 보면 다음과 같다.

public func compactMap<ElementOfResult>(_ transform: (Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult]

아쉽게도 정의에서 알 수 있듯이 compactMap은 전달된 함수(클로저)의 반환 타입이 Optional로 포장 되어 있는 경우에만 Optional을 풀어서 동등한 위상으로 펼친뒤 다시 Functor에 담아 반환한다는 것을 알 수 있다. 위의 예시에서도 볼 수 있듯 값이 nil인 경우 그 값을 제외하고 값이 있는 경우에만 Functor에 담아 반환하는 것을 볼 수 있다.

따라서 아래와 같이 내부 ElementOptional이 아닌 중첩된 형태의 Array인 경우 compactMapflatMap의 적용 결과의 차이를 확인할 수 있다.

let matrix: [[Int]] = [[1, 2, 3], [4, 5, 6]]
let flattenMatrix = matrix.flatMap { $0 }
let anotherMatrix = matrix.compactMap { $0 }

print(flattenMatrix)    //[1, 2, 3, 4, 5, 6]
print(anotherMatrix)    //[[1, 2, 3], [4, 5, 6]]

이때 사용된 flatMap의 정의는 아래와 같으며 SegmentOfResultElement를 가져와 중접된 형태의 Array를 동일한 위상으로 펼친뒤 다시 Functor에 담아 반환해준다.

public func flatMap<SegmentOfResult>(_ transform: (Element) throws -> SegmentOfResult) rethrows -> [SegmentOfResult.Element] where SegmentOfResult : Sequence

compactMap의 경우 내부 ElementOptional로 감싸져 있지 않기 때문에 그대로 다시 Functor에 담아 반환해 준 결과를 보여준다.

profile
새롭게 알게된 것을 기록하는 공간

0개의 댓글