해당 포스트는 모나드란 무엇인가 포스트를 읽고 오시면 이해하는데 도움이 됩니다.
시작에 앞서 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
은 무슨 함수일까?
잠시 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) 펼쳐 동등한 위상으로 만들어 주는 기능을 한다. 동등한 위상으로 만들어 주는 기능은 내부 Element가 Optional
형태로 감싸진 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]
그렇다면 flatMap
과 compactMap
은 완전히 동일한 것일까?
두 함수가 같은지 확인 하기 위해 Array
타입에 정의 되어있는 compactMap
의 형태를 확인해 보면 다음과 같다.
public func compactMap<ElementOfResult>(_ transform: (Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult]
아쉽게도 정의에서 알 수 있듯이 compactMap
은 전달된 함수(클로저)의 반환 타입이 Optional
로 포장 되어 있는 경우에만 Optional
을 풀어서 동등한 위상으로 펼친뒤 다시 Functor에 담아 반환한다는 것을 알 수 있다. 위의 예시에서도 볼 수 있듯 값이 nil
인 경우 그 값을 제외하고 값이 있는 경우에만 Functor에 담아 반환하는 것을 볼 수 있다.
따라서 아래와 같이 내부 Element가 Optional
이 아닌 중첩된 형태의 Array
인 경우 compactMap
과 flatMap
의 적용 결과의 차이를 확인할 수 있다.
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
의 정의는 아래와 같으며 SegmentOfResult
의 Element
를 가져와 중접된 형태의 Array
를 동일한 위상으로 펼친뒤 다시 Functor에 담아 반환해준다.
public func flatMap<SegmentOfResult>(_ transform: (Element) throws -> SegmentOfResult) rethrows -> [SegmentOfResult.Element] where SegmentOfResult : Sequence
compactMap
의 경우 내부 Element가 Optional
로 감싸져 있지 않기 때문에 그대로 다시 Functor에 담아 반환해 준 결과를 보여준다.