OptionalのmapとflatMapを読み解く

Swiftオープンソース化されたことにより、私たちでも標準ライブラリの実装を読むことができるようになりました。そこで、今回はOptional型のmap()flatMap()の2つを読んでみて、どういう違いがあるのかを実装面から考えてみようと思います。

Optional型の定義は、stdlib/public/core/Optional.swift ファイルにあります。

public enum Optional<Wrapped> : _Reflectable, NilLiteralConvertible {
  case None
  case Some(Wrapped)
    :

今更かもしれませんが、Optional型とは様々な型のオブジェクトをSomeのAssociated Valueとして持てるenumのことです。ちなみにWrappedは単なるジェネリックTのtypealiasで、上のcase文のすぐ後で、次のように定義されている型です。

@available(*, unavailable, renamed="Wrapped")
public typealias T = Wrapped

さて、そんなOptional型のmap()flatMap()の2つがどのように実装されているかというと、下に示すようにとてもシンプルな実装になっていました。

  public func map<U>(@noescape f: (Wrapped) throws -> U) rethrows -> U? {
    switch self {
    case .Some(let y):
      return .Some(try f(y))
    case .None:
      return .None
    }
  }

  public func flatMap<U>(@noescape f: (Wrapped) throws -> U?) rethrows -> U? {
    switch self {
    case .Some(let y):
      return try f(y)
    case .None:
      return .None
    }
  }

どちらも.Some.Noneかで処理を分岐させて、.Someのときのみ引数の関数をアンラップした値と共に呼んでいます。ですが、map()は.Someだった場合に実施する結果を再び.SomeでOptionalにしてから返していること、flatMap()は結果をアンラップしたまま返していることが違っています。その結果としてmap()はOptional型のままの値が得られ、一方でflatMap()はアンラップした値に変わります。

というわけで、実装から見たOptional型のmapとflatMapの違いは、結果をアンラップするかどうかだけ、と解釈できそうです。