はじめに
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で説明してみた」。 ↩