便利だけど理解が難しいmap
とflatMap
ですが、次のように捉えると自分としてはしっくりきました。
正確な表現ではないかもしれませんが、理解の参考になればと思います。
説明のための用語
map
やflatMap
が呼ばれるインスタンスを「レシーバ」と呼ぶことにします。
instance.map(closure)
instance.flatMap(closure)
と呼ぶ時のinstance
がレシーバです。
map
でやっていることを整理する
map
は配列の各要素を変換して配列で返すイメージが強いと思いますが、
もう少し一般化して、次のようなステップを踏む処理だと捉えます。
1. レシーバのジェネリクスを解除してクロージャに渡す。
ここで言う「ジェネリクスの解除」とは、オプショナルならアンラップ、SequenceTypeなら個々の要素ごとにバラす事を表します。
2. 得られた値をクロージャ内の処理で変換する。
1で得られた値をクロージャに渡し、変換した値を返してもらいます。
3. 変換結果に対して、レシーバの型に対応するジェネリクスを再設定する。
対応するジェネリクスとは、レシーバがOptionalならOptional、SequenceTypeならArrayになります。
つまり、2で受けとった値をオプショナルにしたり、値をまとめて配列にしたりします。
図で表すと次のようなイメージです。
flatMap
とは
map
の2のステップの後、クロージャから渡された値に対して、
ジェネリクスを解除してから3の処理に進むのがflatMap
です。
2と3の間で1と同じことをするイメージです。
ここで、レシーバが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)
map
とflatMap
に渡しているクロージャは共通です。
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]
ここでもmap
とflatMap
に渡しているクロージャは共通です。
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]
内容は変わりましたが、map
とflatMap
に渡しているクロージャは共通です。
map
はクロージャから配列を受け取り、そのまま3のステップに進むので、map
の返り値は[[Int]]
です。
一方flatMap
は、クロージャから配列を受け取り、個々の要素に分解してから3のステップに進むため、flatMap
の返り値の型は[Int]
となります。
最後に
いかがだったでしょうか?少しでも理解の参考になれば幸いです。