Swift 2.0の新しいflatMapが便利過ぎる

次のような `String` の `Array` があったとき、各要素を `Int` に変換するにはどうすれば良いでしょうか？ただし、 `Int` に変換できない要素は結果から除くものとします。

```swift
let strings = ["2", "3", "four", "5"]
let numbers = ... // 結果は [2, 3, 5]
```

`for` ループで回すとかは面倒すぎるのでなしです。

## reduce

最初に思いつきそうな方法は、万能の高階関数 `reduce` を使う方法です。

```swift
let numbers = strings.reduce([Int]()) { (var numbers, string) in
	if let number = Int(string) {
		numbers.append(number)
	}
	return numbers
}
```

なんか、 `for` ループを回すのと変わらない面倒さです・・・。

## map & filter

パースできないものは取り除いて変換したいんだから、 `map` と `filter` を組み合わせれば良さそうです。

```swift
let numbers = strings.map { Int($0) }
	.filter { $0 != nil }
```

とてもシンプルになりました。

しかし、ちょっと待って下さい！！これでは `numbers` の型が `[Int]` ではなく `[Int?]` になってしまいます。仕方ないので型の変換を行う `map` を付け足しましょう。

```swift
let numbers = strings.map { Int($0) }
	.filter { $0 != nil }
	.map { $0! }
```

ずいぶんと不格好になってしまいました・・・。しかも `nil` のチェックと `!` が二重に入るのは許せません。

## flatMap (Swift 1.2)

そんなときに役に立つのが `flatMap` でした。変換に失敗した `nil` を空の `Array` に置き換えることで、その要素を消すことができるのです。

```swift
let numbers = strings.flatMap { Int($0) ?? [] } // コンパイルエラー
```

おっと、これでは `Int($0)` で変換に成功したときが `Array` になっていないので `flatMap` できません。少し不格好ですが次のようにして、成功した場合も `[Int]` に変換しましょう。

```swift
let numbers = strings.flatMap { Int($0).map { [$0] } ?? [] }
```

気をつけてほしいのは、この `map` は `Array` ではなく `Optional` の `map` だということです。

`.map { [$0] } ?? []` でやっていることは、 `Optional` が値を持っていれば 1 要素の `Array` に、 `nil` であれば空の `Array` に変換するということです。これは、 ["SwiftのOptional型を極める"](http://qiita.com/koher/items/c6f446bad54442a28bf4) で[書いた](http://qiita.com/koher/items/c6f446bad54442a28bf4#array%E3%81%A8optional)ように __`Optional` を要素が 1 個か 0 個の `Array` と考えることができるという話とリンクしています。 `Optional` を `Array` に見立てることで `flatMap` で flat にできるわけです。__

このパターンは頻出で、型を変換しながら変換に失敗した要素は取り除きたいという場合に必須のイディオムでした。しかし、 `Optional` を `Array` に変換するためにわざわざ `.map { [$0] } ?? []` と書くのは面倒です。

## flatMap (Swift 2.0)

これを解決してくれるのが __Swift  2.0 で導入された新しい `flatMap`__ です。新しい `flatMap` を使えば次のように書けます。

```swift
let numbers = strings.flatMap { Int($0) }
```

めちゃくちゃシンプルですね！！

この `flatMap` の宣言は次のようになっています（[リファレンス](https://developer.apple.com/library/prerelease/ios//documentation/Swift/Reference/Swift_SequenceType_Protocol/index.html#//apple_ref/swift/intfm/SequenceType/s:FeRq_Ss12SequenceType_SsS_7flatMapu__Rq_S__Fq_FFqqq_S_9GeneratorSs13GeneratorType7ElementGSqqd___GSaqd___)）。

```swift
// protocol SequenceType
func flatMap<T>(@noescape _ transform: (Self.Generator.Element) -> T?) -> [T]
```

つまり、 __`Optional` を 1 要素か 0 要素の `Array` と見なすという話を、 `flatMap` のオーバーロードで実現__ してくれているわけです。

これで型の変換がはかどりますね！

## より現実的なケース

`Item` というクラスがあり、そのオブジェクトが JSON ファイルで保存されているとします。今、複数の JSON ファイルのパスを `[String]` で保持しているとして、それらの指す JSON ファイルをパースして `[Item]` を読み込むケースを考えてみましょう。

おそらく、次のような処理手順を思い浮かべるはずです[^1]。

1. `path` の `String` から `NSData` を読み込む。
2. `NSData` から `JSON` オブジェクトを生成する。
3. `JSON` をパースして `Item` オブジェクトを生成する。

上記は一つのファイルから一つの `Item` オブジェクトを生成する処理ですが、 `map` と `flatMap` を使えば、この処理手順をそのまま複数の（ `Array` の）処理として書くことができます。

```swift
return paths.flatMap { NSData(contentsOfFile: $0) } // [String] -> [NSData]
    .map { JSON(data: $0) }                         // [NSData] -> [JSON]
    .flatMap { Item.fromJSON($0) }                  // [JSON] -> [Item]
```

1 行目はパスの `[String]` を `[NSData]` に変換していますが、 `NSData(contentsOfFile:)` は Failable initializer なので戻り値は `NSData?` です。もし `map` を使っていたら戻り値の型は　`[NSData?]` になってしまいますが、 `flatMap` を使って flat にすることで `[NSData]` を得ることができます。これは、 `Optional` を `Array` とみなして、 `[[NSData]]` が flat にされて `[NSData]` になったとも考えられます。

2 行目の `JSON` の Initializer は失敗しないので、ただの `map` で十分です。

3 行目の `Item.formJSON()` はパースに失敗する可能性があるので戻り値の型は `Item?` です。そのため、ここでも 1 行目同様に `flatMap` が必要になります。

このように、途中で失敗する可能性があるような処理でも、新しい `flatMap` を使うことで簡単に扱うことができるようになります。

### 単数の場合

余談ですが、単数の場合でも `Optional` の `map` と `flatMap` を使って同じように書くことができます。これは、 `Optional` を要素数 0 か 1 の `Array` と見なすことができるという話からも当然のことです。

```swift
return NSData(contentsOfFile: path) // String -> NSData?
    .map { JSON(data: $0) }         // NSData? -> JSON?
    .flatMap { Item.fromJSON($0) }  // JSON? -> Item?
```

2 行目は失敗する可能性がないので `map` 、 3 行目は失敗する可能性があるので `flatMap` となります。

## Swift 2.0 の新しい flatMap を Swift 1.2 で実装してみる

同じものを Extension で作ってやれば Swift 1.2 でも使えるようになるので [swiftf](https://github.com/koher/swiftf/tree/swift-1.2) に[実装してみました](https://github.com/koher/swiftf/blob/a37b604ef5f627a7fce46e8220493109a7cb4eac/Project/swiftf/swiftf.swift#L27-L29)。

```swift
extension Array {
	func flatMap<U>(@noescape transform: T -> U?) -> [U] {
		return flatMap { transform($0).map { [$0] } ?? [] }
	}
}

let strings: [String] = ["2", "3", "four", "5"]
let numbers = strings.flatMap { $0.toInt() } // [2, 3, 5]
```

----------------------------------

[^1]: JSON は [SwiftyJSON](https://github.com/SwiftyJSON/SwiftyJSON) を使ってパースすることを想定しています。