最近になってmapやflatMapの勉強をしたのでその辺りを書いてみます。
mapやflatMapなんですが配列にもOptionalにもあるしObjective-cにはないメソッドなので最初見た時は少し混乱しました。
それぞれ別物と捉えて1つ1つについて調べていったら分かりやすかったので、その視点で記事を書いていきます。
今回はmapは2種類、flatMapは3種類に分類しました。
map(配列用)
1つ目のmapは配列のメソッドです。(正確にはSequenceTypeやらCollectionTypeにあるメソッドなので配列以外でも使えますが)
配列のmapは配列の要素1つ1つに操作をした結果の配列を返します。
[1, 2, 3].map { $0 * 2 } // → [2, 4, 6]
["3", "2", "1"].map { Int($0) } // → [Optional(3), Optional(2), Optional(1)]
クラスの配列からID一覧を取得したりする時に便利でした。
class MyClass {
var id: Int
init(id: Int) {
self.id = id
}
}
let myClasses = [MyClass(id: 1), MyClass(id: 2), MyClass(id: 3)]
myClasses.map { $0.id } // → [1, 2, 3]
map(Optional用)
mapメソッドはOptionalにもあります。
中身がnilなら何もせず、中身がnil以外なら処理をするものです。
let value: Int? = 1
var result: Int? = value.map { $0 + 1 } // → Optional(2)
// ↑ と ↓ は同じ意味
result = value != nil ? value! + 1 : nil // → Optional(2)
便利ではあるのですが、自分はif let
やguard
でnilを除外する事が多いので配列のmapに比べると利用頻度は低いです。
let optionalValue: Int? = 1
guard value = optionalValue else { return }
result = value + 1 // → 1
flatMap(2重の配列用)
次にflatMapについて書きます。
flatMapはmapのような動きをして、更に2重の配列を1重に直してくれます。
[[1, 2], [3]].flatMap { $0 } // → [1, 2, 3]
[[1, 2], [3]].flatMap { $0 + [1] } // → [1, 2, 1, 3, 1]
mapで同じ事をすると下の結果になります。
[[1, 2], [3]].map { $0 } // → [[1, 2], [3]]
[[1, 2], [3]].map { $0 + [1] } // → [[1, 2, 1], [3, 1]]
flatMap(Optional用)
flatMapメソッドはOptional型にも定義されています。
配列のflatMapはmapをした上で2重の配列を1重にしてくれましたが、OptionalのflatMapはmapをした上で結果をアンラップします。
let value: String? = "1"
// mapだと2重のOptionalになってしまう
value.map { Int($0) } // Optional(Optional(1))
// flatMapならただのOptionalになる
value.flatMap { Int($0) } // Optional(1)
ブロックの結果がnilの時はnilを返します。
let value: String? = ""
print(value.map { Int($0) }) // Optional(nil)
print(value.flatMap { Int($0) }) // nil
クロージャーがOptionalを返さない場合はどちらも同じ結果になります。
var value: Int? = nil
value.flatMap { $0 + 1 } // → nil
value = 1
value.flatMap { $0 + 1 } // → Optional(2)
value = nil
value.map { $0 + 1 } // → nil
value = 1
value.map { $0 + 1 } // → Optional(2)
flatMap(中がオプショナルな配列用)
最後にSwift2.0で追加されたflatMapです。
中身がOptionalの配列を中身をアンラップした配列にしてくれます。
["1", "2", "3"].map { Int($0) } // → [Optiona(1), Optiona(2), Optiona(3)]
["1", "2", "3"].flatMap { Int($0) } // → [1, 2, 3]
nilがある場合はnilを除外してくれます。
["1", "2", "string", "3"].map { Int($0) } // → [Optiona(1), Optiona(2), nil, Optiona(3)]
["1", "2", "string", "3"].flatMap { Int($0) } // → [1, 2, 3]
配列からnilを取り除く時も便利です。
[1, 2, nil, 3].flatMap { $0 } // → [1, 2, 3]
2重で中身がOptionalな配列にflatMapを使うとどうなるか
先ほどflatMapは2重の配列を1重の配列に直すものと中身のOptionalを取り出す2種類があると書きました。
[1, 2, 3, nil].flatMap { $0 } // → [1, 2, 3]
[[1], [2, 3]].flatMap { $0 } // → [1, 2, 3]
2重で中身がOptionalな配列にflatMapを使ったらどうなるか試してみました。
結果は下の通りで、Optionalをアンラップする方が優先されました。
[[1], [2, 3], nil].flatMap { $0 } // → [[1], [2, 3]]
もう一度flatMapを使えば2重の配列の中身を取り出せました。
[[1], [2, 3], nil].flatMap { $0 }.flatMap { $0 } // → [1, 2, 3]
mapとflatMapが同じ結果になるのはなぜか
mapとflatMapは場合によっては同じ値を返します。
なぜ同じ結果になるかを調べてみました。
[1, 2, 3].flatMap { $0 + 1 } // → [2, 3, 4]
[1, 2, 3].map { $0 + 1 } // → [2, 3, 4]
下の式を見ると分かるのですが、flatMapの戻り値がInt?になっています。
つまり下のようなケースでは中身がオプショナルな配列用のflatMapが使われます。
そして戻り値は通常のInt型がOptionalに変換されたものが返り、それがアンラップされるからmapと同様の結果になるようです。
[1, 2, 3].flatMap { (value: Int) -> Int? in
return value + 1
}
[1, 2, 3].map { (value: Int) -> Int in
return value + 1
}