Swift/개념 & 응용

[Swift] flatMap이 deprecated가 된 이유 / compactMap의 등장

유정주 2024. 10. 27. 15:30
반응형

서론

Swift 4.1에서 flatMap이 deprecated되고 compactMap으로 역할이 분리되었습니다.

저는 한 가지 착각하고 있었습니다. "flatMap이 아예 사라지고 compactMap이 새로 생겼구나"라고 오해했는데요, flatMap은 여러 형태가 존재했고, flatMap 중 하나가 compactMap이 된거였습니다.

이번 포스팅은 pointfree의 https://www.pointfree.co/episodes/ep10-a-tale-of-two-flat-maps 영상과 스크립트를 정리한 포스팅입니다. flatMap이 어떤 형태를 제공했었고, 어떤 메서드가 compactMap이 된 것인지 알아보겠습니다.

 

flatMap의 세 가지 형태

flatMap은 총 세 가지 형태로 제공됩니다.

extension Array {
  func flatMap<B>(_ f: @escaping (Element) -> [B]) -> [B]
}

// 예시
let nested = [[1, 2], [3, 4], [5, 6]]
let flattened = nested.flatMap { $0 }  // [1, 2, 3, 4, 5, 6]

가장 기본적인 형태의 flatMap입니다. 중첩된 배열을 평탄화하는데 사용됩니다.

 

extension Optional {
  func flatMap<B>(_ f: @escaping (Element) -> B?) -> B?
}

// 예시
let stringNumber = "42"
let result = Optional(stringNumber).flatMap(Int.init)  // Optional(42)

옵셔널 체이닝을 위한 flatMap입니다. 중첩된 옵셔널을 단일 옵셔널로 만들어줍니다.

 

extension Array {
  func flatMap<B>(_ transform: (Element) -> B?) -> [B]
}

배열의 각 요소를 변환하면서 nil을 제거하는 역할을 합니다.

이 버전의 flatMap이 오늘의 주인공입니다. 

 

문제점

세 가지 형태 중 세 번째 flatMap은 어떤 문제가 있었을까요?

 

먼저 타입 시그니처의 불일치입니다.

// 1번: Array -> Array
flatMap: ((A) -> [B]) -> ([A]) -> [B]

// 2번: Optional -> Optional      
flatMap: ((A) -> B?) -> (A?) -> B?

// 3번: Array + Optional -> Array (???)
flatMap: ((A) -> B?) -> ([A]) -> [B]

 

세 번째 flatMap은 Array와 Optional 두 가지 컨테이너 타입을 동시에 다루고 있습니다. 이는 함수형 프로그래밍 관점에선 일관성이 떨어지는 설계입니다.

 

두 번째는 옵셔널 처리의 모호함 때문입니다.

let numbers = [1, 2, 3]
numbers.flatMap { $0 + 1 }  // [2, 3, 4]

이 코드는 컴파일러가 자동으로 반환값을 .some($0 + 1)로 감싸줍니다. 이는 map을 사용해야할 상황에서도 flatMap이 동작하게 만들어서 코드의 의도를 모호하게 합니다.

 

코드의 모호함은 예기치 않은 동작으로 이어집니다.

struct User {
    let name: String?
}

let users = [User(name: "Blob"), User(name: "Math")]
users.flatMap { $0.name }  // ["Blob", "Math"]

// 타입 변경 후
struct User {
    let name: String  // Optional 제거
}

users.flatMap { $0.name }  // ["B", "l", "o", "b", "M", "a", "t", "h"]

User 구조체의 name 타입에서 옵셔널을 제거했을 뿐인데 전혀 다른 결과가 나왔습니다.

 

compactMap 등장

이에 Swift 4.1에서 문제가 되었던 세 번째 flatMap을 deprecated하고 compactMap 메서드를 새로 제공했습니다.

extension Array {
    func compactMap<B>(_ transform: (Element) -> B?) -> [B] {
        var result = [B]()
        for x in self {
            switch transform(x) {
            case let .some(x): result.append(x)
            case .none: continue
            }
        }
        return result
    }
}

filterMap이라는 네이밍도 후보에 있었는데요. Ruby의 compact 메서드에 영감을 받아서 compactMap이 되었다고 하네요 ㅎㅎ

compactMap이 제공되면서 배열을 압축한다는 의미가 더 명확해졌고, flatMap의 명확성도 높아졌습니다.

 

때로는 작은 네이밍 변경이 큰 변화를 가져올 수 있습니다. flatMap에서 compactMap으로 네이밍이 변경되어 Swift의 타입 시스템을 더 일관되고 예측 가능하게 만들었습니다. 우리에게 좋은 네이밍이 얼마나 중요한지를 다시 한 번 일깨워주는 좋은 사례인 거 같네요.

 

감사합니다.


아직은 초보 개발자입니다.

더 효율적인 코드 훈수 환영합니다!

공감 댓글 부탁드립니다.

반응형