LoginSignup
69
71

More than 5 years have passed since last update.

SwiftのmapとflatMapがやっている事を理解する

Last updated at Posted at 2016-01-30

便利だけど理解が難しいmapflatMapですが、次のように捉えると自分としてはしっくりきました。
正確な表現ではないかもしれませんが、理解の参考になればと思います。

説明のための用語

mapflatMapが呼ばれるインスタンスを「レシーバ」と呼ぶことにします。

instance.map(closure)
instance.flatMap(closure)

と呼ぶ時のinstanceがレシーバです。

mapでやっていることを整理する

mapは配列の各要素を変換して配列で返すイメージが強いと思いますが、
もう少し一般化して、次のようなステップを踏む処理だと捉えます。

1. レシーバのジェネリクスを解除してクロージャに渡す。

ここで言う「ジェネリクスの解除」とは、オプショナルならアンラップ、SequenceTypeなら個々の要素ごとにバラす事を表します。

2. 得られた値をクロージャ内の処理で変換する。

1で得られた値をクロージャに渡し、変換した値を返してもらいます。

3. 変換結果に対して、レシーバの型に対応するジェネリクスを再設定する。

対応するジェネリクスとは、レシーバがOptionalならOptional、SequenceTypeならArrayになります。
つまり、2で受けとった値をオプショナルにしたり、値をまとめて配列にしたりします。

図で表すと次のようなイメージです。

qiita_fegures.001.jpeg

flatMapとは

mapの2のステップの後、クロージャから渡された値に対して、
ジェネリクスを解除してから3の処理に進むのがflatMapです。

2と3の間で1と同じことをするイメージです。

qiita_fegures.002.jpeg

ここで、レシーバがSequenceTypeの場合、ジェネリクス解除ができないと(例えばnilをアンラップしようとする)、その値は返り値の配列から除外されます。

具体例

具体例を示します。
ここで使用しているInt(String)の返り値はInt?であることを踏まえて読んでください。

オプショナルの場合

let numberString: String? = "123"

let mapNumber = numberString.map { (s: String) -> Int? in
    return Int(s)
}
print(mapNumber) // -> Optional(Optional(123))

let flatMapNumber = numberString.flatMap { (s: String) -> Int? in
    return Int(s)
}
print(flatMapNumber) // -> Optional(123)

mapflatMapに渡しているクロージャは共通です。

mapはクロージャの返り値のオプショナルをアンラップせずに3のステップに進むため、mapの返り値はOptional(Optional(Int))です。
一方flatMapはアンラップするため、flatMapの返り値の型はOptional<Int>です。

配列の場合

クロージャの返り値がオプショナル

let numberStrings: [String] = ["123", "456", "789"]

let mapNumbers = numberStrings.map { (s: String) -> Int? in
    return Int(s)
}
print(mapNumbers) // -> [Optional(123), Optional(456), Optional(789)]

let flatMapNumbers = numberStrings.flatMap { (s: String) -> Int? in
    return Int(s)
}
print(flatMapNumbers) // -> [123, 456, 789]

ここでもmapflatMapに渡しているクロージャは共通です。

mapはクロージャの返り値のオプショナルをアンラップせずに3のステップに進むため、mapの返り値は[Int?]です。
一方flatMapはアンラップするため、flatMapの返り値の型は[Int]です。

クロージャの返り値がSequenceType

let numbers: [Int] = [123, 456, 789]

let mapNumbers = numbers.map { (number: Int) -> [Int] in
    return [number, number]
}
print(mapNumbers) // -> [[123, 123], [456, 456], [789, 789]]

let flatMapNumbers = numbers.flatMap { (number: Int) -> [Int] in
    return [number, number]
}
print(flatMapNumbers) // -> [123, 123, 456, 456, 789, 789]

内容は変わりましたが、mapflatMapに渡しているクロージャは共通です。

mapはクロージャから配列を受け取り、そのまま3のステップに進むので、mapの返り値は[[Int]]です。
一方flatMapは、クロージャから配列を受け取り、個々の要素に分解してから3のステップに進むため、flatMapの返り値の型は[Int]となります。

最後に

いかがだったでしょうか?少しでも理解の参考になれば幸いです。

参考

69
71
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
69
71