[Swift4.1] compactMap(_:)メソッドの導入経緯のご紹介

こちらのプロポーサルを検証してまとめました。

非推奨になったメソッド

Swift4.1から以下のflatMap(_:)メソッドが非推奨となりました。

Sequence
func flatMap<U>(_:(Element) -> U?) -> [U]

あわせてこちらも非推奨になった。

Collection
func flatMap(_: (Element) -> String?) -> [String]

追加されたメソッド

代わりに以下のメソッドが追加されました。

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

よく見ると、どうやらメソッド名がリネームされただけのようです。
どうしてわざわざリネームしたのでしょうか?

動機

上記の非推奨にされたflatMap(:_)メソッドは、以下のように配列の値を変更しつつ、フィルタリングも行いたい時に使用されていた方が多いかと思います。
(mapはフィルタリングできないし、filterは型の変換が行えない。ところがこいつは両方行える。)

文字列から数字のみ取り出す
 10> "H1e2l3l4o5Wo7l8d".flatMap { Int.init(String($0)) }
$R9: [Int] = 7 values {
  [0] = 1
  [1] = 2
  [2] = 3
  [3] = 4
  [4] = 5
  [5] = 7
  [6] = 8
}

上記、 Int.init の結果が失敗した場合、nilが返され、その値は出力される配列から除外されます。

しかしながら、本来のflatMap(_:)メソッドは、Haskellのリストモナドが由来となっており、今回その由来元のメソッドは無事に残されております。

今回残ったflatMap(_:)メソッド

Sequence
Sequence.flatMap<S>(_: (Element) -> S) -> [S.Element] where S: Sequence

上記のメソッドの役割は、リストモナドで検索をかけていただけたらと思います。

ではなぜその他の2つのflatMap(_:)メソッドを非推奨にして、代わりにあらたにリネームしただけの関数を追加したかと言うと以下の問題がswift4で発生するようになったためです。

swift4から StringSeaquence となった。

従って、以下names1とnames2は、同様の結果を返すように見えますが、異なる結果が返る用になりました。

配列から名前を取り出す
struct Person {
    var name: String
    init(_ name: String) { self.name = name }
}

func getNames(_ people: [Person]) -> [String] {
    return people.flatMap { $0.name }
}

let people = [Person("tanaka"), Person("yoshida")]
let names1 = getNames(people)
print("names1: \(names1)")
let names2 = people.flatMap { $0.name }
print("names2: \(names2)")
swift3.1での実行結果
names1: ["tanaka", "yoshida"]
names2: ["tanaka", "yoshida"]
swift4.0での実行結果
names1: ["tanaka", "yoshida"]
names2: ["t", "a", "n", "a", "k", "a", "y",”o” "s", "h", "i", "d", "a"]

Swift4以前は、StringはSequeceではなかったため、
どちらもCollection側の flatMap(_:) メソッドが使用されたのに対し、Swift4.0の方では、 names1 が非推奨にされたCollection側の flatMap(_:) メソッドが使われ、 names2 は、 今回残った本来の flatMap(_:) メソッドが使用されたためです。

結論

とういこともあり、今回非推奨にした flatMap(_:) メソッドは本来のリストモナドのための flatMap(_:) メソッドと名前がかぶっておかしいし、Swift4からStringがSequenceになって更に上記のメソッドとぶつかってしまい、紛らわしいしということもあり、 compactMap(_:) へとリネームされてしまいました。

参照

swift-evolution/0187-introduce-filtermap.md at master · apple/swift-evolution

Introduce Sequence.compactMap(_:)

flatMap(_:) - Sequence | Apple Developer Documentation

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

flatMap(_:) - Sequence | Apple Developer Documentation

func flatMap(_ transform: (Self.Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult]

flatMap(_:) - Array | Apple Developer Documentation

func flatMap(_ transform: (Element) throws -> String?) rethrows -> [String]

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.