ArrayとOptionalのmapは同じです

  • 54
    Like
  • 2
    Comment
More than 1 year has passed since last update.

Optional型にも map() メソッドがあるのですね。
mapがどういう存在なのか、ますます分からなくなってきました。

Swiftの全関数一覧@tottokotkd さんからこのような返信をいただいたので、 ArrayOptionalmap メソッド/関数は概念として同じものだ という内容のコメントを書きました。他にも同様の疑問を持っている人がいるかもしれないと思い、本投稿は上記投稿へのコメントを加筆・修正して、独立した記事として投稿したものです。

mapとは何か

OptionalArray を値を入れる箱だと考えると、 map は箱から値を取り出さずに操作するためのメソッド/関数です(正確には、操作した結果を新しい箱に格納して返します)。

Optionalの場合

例えば、

let a: Optional<Int> = 3 // Optional<Int> は Int? と等価

があったとして、箱 Optional の中身 Int を二乗したい場合は次のようにできます。

let b: Optional<Int> = a.map { $0 * $0 } // Optional(9)

anil のときは結果も 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の場合

Arraymap も同様です。次のコードは箱 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 } // []

このように、 ArrayOptional を対応させて考えることができ、それらの map は同じ概念を表しています

(おまけ)より高度な抽象化とflatMap

ArrayOptional は共に ファンクターモナド であり、より高度な抽象化ができます( mapファンクター と関係があります。そのうち詳しい説明を書くかもしれません)。 → "モナドについてSwiftで説明してみた"

特に モナド の持つ flatMapbind などと呼ばれるメソッド/関数はとても便利です。 flatMapOptional Chaining と似ていますが、 Optional Chaining でできないこともできます。

ArrayのflatMap
// 要素の数だけ要素を繰り返した Array を作る
[1, 2, 3].flatMap { [Int](count: $0, repeatedValue: $0) } // [1, 2, 2, 3, 3, 3]
OptionalのflatMap
// 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の関係

上記のコードの flatMapmap に置き換えてみるとどうなるでしょうか。

Array
[1, 2, 3].map { [Int](count: $0, repeatedValue: $0) } // [[1], [2, 2], [3, 3, 3]]
Optional
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を混ぜて使う

ArrayOptional を混ぜて使うための flatMap が Swift 2.0 で追加されました。 → "Swift 2.0の新しいflatMapが便利過ぎる"

flatMapの入れ子の代わりにアプリカティブ

さらに余談ですが、 OptionalflatMap の例でやった Optional な値同士の計算は、前に書いたようにアプリカティブを使う方法もあります。

let a: Int? = 2
let b: Int? = 3
(+) <%> a <*> b // Optional(5)