はじめに
Swiftにおけるmap
やflatMap
についての記事はQiitaにもたくさんありますが、Array
とOptional
に限定されていて且つそれぞれ別々に説明されている記事が多いような印象を受けました(主観)。
そこで、(自分が新しく作成した型についてmap
で実装すべきかflatMap
で実装すべきか迷った時に参照するためにも)この記事ではmap
とflatMap
の特性について簡潔に 一般化された説明をしてみようという次第です。すごく詳細に一般化しようとすると「モナド」というものを理解しないといけないようですが、それは別の記事にお任せしましょう1。
既に同様の記事があったらごめんなさい。あと、この記事ではSwiftに限った話をしていますので悪しからず…。
総論
map
もflatMap
も関数(クロージャ)を引数に取るいわゆる高階関数と呼ばれるものですが、元の型・引数となる関数(クロージャ)における返り値の型・高階関数の返り値の型に一定の関係性があり、その関係性によって区別がなされます。「二重が一重になるのがflat」みたいなのは一旦忘れてください。
各論
map
とは
動作
-
Hoge<T>
の場合:(T) -> U
という関数(クロージャ)を引数にとって、Hoge<U>
を返す。 -
Fuga<T1, T2>
の場合:(T1) -> U
という関数(クロージャ)を引数にとって、Fuga<U, T2>
を返す
即ち、map
は「引数となる関数(クロージャ)の返り値を同じ型で包み直す」ということです。
例
-
Array<Element>
:func map<T>(_ transform: (Element) throws -> T) rethrows -> Array<T>
-
Optional<Wrapped>
:func map<U>(_ transform: (Wrapped) throws -> U) rethrows -> Optional<U>
-
Result<Success, Failure>
:func map<NewSuccess>(_ transform: (Success) -> NewSuccess) -> Result<NewSuccess, Failure>
反例…?
-
String
:func map<T>(_ transform: (Character) throws -> T) rethrows -> Array<T>
-
Dictionary
:func map<T>(_ transform: ((key: Key, value: Value)) throws -> T) rethrows -> Array<T>
➡️これらはSequence
由来なので、Sequecne<Element>
において(Element) -> T
という関数(クロージャ)を引数にとってSequence<T>
を返すと考えれば矛盾はありません。
flatMap
とは
動作
-
Hoge<T>
の場合:(T) -> Hoge<U>
という関数(クロージャ)を引数にとって、Hoge<U>
を返す。 -
Fuga<T1, T2>
の場合:(T1) -> Fuga<U, T2>
という関数(クロージャ)を引数にとって、Fuga<U, T2>
を返す
即ち、「引数となる関数(クロージャ)の返り値とflatMap
自身の返り値は型が同じ」ということです。
例
-
Optional<Wrapped>
:func flatMap<U>(_ transform: (Wrapped) throws -> Optional<U>) rethrows -> Optional<U>
-
Result<Success, Failure>
:func flatMap<NewSuccess>(_ transform: (Success) -> Result<NewSuccess, Failure>) -> Result<NewSuccess, Failure>
あれ?Arrayは…?
Array<Element>
でflatMap
はfunc flatMap<SegmentOfResult>(_ transform: (Element) throws -> SegmentOfResult) rethrows -> [SegmentOfResult.Element] where SegmentOfResult: Sequence
となっています。なので、これはArray
に対するflatMap
ではなく、前述したようなString
と同様、Sequence<Element>
に対するflatMap
と捉えなければなりません。つまり、flatMap
の引数となる関数(クロージャ)は(Element) -> Sequence<U>
と考えることができ、そしてflatMap
の返り値もSequence<U>
と解釈できます。
注意
昔のSwiftではflatMap
という名前で現在のcompactMap
と同じ動作が実装されていました。昔のドキュメントなどを参照して混乱なきよう…。
ちなみにcompactMap
はSequence<Element>
において.map(_:(Element) -> T?).filter({ $0 != nil })
というような動作をします(正しいコードではなくあくまでイメージ)。
エピローグ
Result
を実装するPR内でAppleの中の人がflatMap
についてコメントしていた内容にインスパイアされた記事でした。flatMap
を「二重になったものを一重にするんだ」と思っていると、Result
におけるflatMap
が「??」となりそうだなと。
-
たとえば、「モナドについてSwiftで説明してみた」。 ↩