Optional型にも map() メソッドがあるのですね。
mapがどういう存在なのか、ますます分からなくなってきました。
Swiftの全関数一覧で @tottokotkd さんからこのような返信をいただいたので、 Array と Optional の map メソッド/関数は概念として同じものだ という内容のコメントを書きました。他にも同様の疑問を持っている人がいるかもしれないと思い、本投稿は上記投稿へのコメントを加筆・修正して、独立した記事として投稿したものです。
mapとは何か
Optional や Array を値を入れる箱だと考えると、 map は箱から値を取り出さずに操作するためのメソッド/関数です(正確には、操作した結果を新しい箱に格納して返します)。
Optionalの場合
例えば、
let a: Optional<Int> = 3 // Optional<Int> は Int? と等価
があったとして、箱 Optional の中身 Int を二乗したい場合は次のようにできます。
let b: Optional<Int> = a.map { $0 * $0 } // Optional(9)
a が nil のときは結果も nil になります。
let a: Optional<Int> = nil
let b: Optional<Int> = a.map { $0 * $0 } // nil
もし中身を取り出してこれをやろうとすると次のようになり非常に面倒です(しかも let でなくなってしまいました…)。
let a: Optional<Int> = 3
var b: Optional<Int>
if let a0 = a {
b = a0 * a0
} else {
b = nil
}
Arrayの場合
Array の map も同様です。次のコードは箱 Array の中身 Int を箱から取り出さずに二乗します。
let a: Array<Int> = [2, 3, 5] // Array<Int> は [Int] と等価
let b: Array<Int> = a.map { $0 * $0 } // [4, 9, 25]
ArrayとOptionalの関係
Optional は最大で一つしか要素を入れられない Array と考えることができます。上記の Optional のコードを Array で表すと次のようになります。
let a: Array<Int> = [3] // Optional(3) 相当
let b: Array<Int> = a.map { $0 * $0 } // [9]
let a: Array<Int> = [] // nil 相当
let b: Array<Int> = a.map { $0 * $0 } // []
このように、 Array と Optional を対応させて考えることができ、それらの map は同じ概念を表しています。
(おまけ)より高度な抽象化とflatMap
Array と Optional は共に ファンクター や モナド であり、より高度な抽象化ができます( map は ファンクター と関係があります。そのうち詳しい説明を書くかもしれません)。 → "モナドについてSwiftで説明してみた"
特に モナド の持つ flatMap や bind などと呼ばれるメソッド/関数はとても便利です。 flatMap は Optional Chaining と似ていますが、 Optional Chaining でできないこともできます。
// 要素の数だけ要素を繰り返した Array を作る
[1, 2, 3].flatMap { [Int](count: $0, repeatedValue: $0) } // [1, 2, 2, 3, 3, 3]
// Optional な値同士を計算する
let a: Optional<Int> = 2
let b: Optional<Int> = 3
a.flatMap { a0 in b.flatMap { b0 in a0 + b0 } } // Optional(5)
mapとflatMapの関係
上記のコードの flatMap を map に置き換えてみるとどうなるでしょうか。
[1, 2, 3].map { [Int](count: $0, repeatedValue: $0) } // [[1], [2, 2], [3, 3, 3]]
let a: Optional<Int> = 2
let b: Optional<Int> = 3
a.map { a0 in b.map { b0 in a0 + b0 } } // Optional(Optional(5))
前者の戻り値の型は Array<Array<Int>> 、後者の戻り値の型は Optional<Optional<Int>> です。二重に箱に入ってしまいました。 flatMap はこの二重の箱を flat に(一重に)してくれるから flatMap なのです。
flatMap の型は次の通りです。
extension Array {
func flatMap<U>(transform: T -> [U]) -> [U]
}
extension Optional {
func flatMap<U>(transform: T -> U?) -> U?
}
ArrayとOptionalを混ぜて使う
Array と Optional を混ぜて使うための flatMap が Swift 2.0 で追加されました。 → "Swift 2.0の新しいflatMapが便利過ぎる"
flatMapの入れ子の代わりにアプリカティブ
さらに余談ですが、 Optional の flatMap の例でやった Optional な値同士の計算は、前に書いたようにアプリカティブを使う方法もあります。
let a: Int? = 2
let b: Int? = 3
(+) <%> a <*> b // Optional(5)