Edited at

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

More than 1 year has passed since last update.

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


非推奨になったメソッド

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から StringSequence となった。

従って、以下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]