Help us understand the problem. What is going on with this article?

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

More than 3 years have passed since last update.

便利だけど理解が難しい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]となります。

最後に

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

参考

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away