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)