Posted at

"map", "reduce", "filter"を書かない方法

More than 3 years have passed since last update.

SwiftではObjective-Cになかったmap/reduce/filterが標準で提供され,お手軽にリスト操作ができるようになりました.

しかし,これらの関数は最近のモダンな言語なら大体用意されているものです.せっかくなのでSwiftらしいことがしたい!

ということで,やはりSwiftといえばその強固な型システムです.そこで上の3関数を振り返ってみると,当然ですが全て型が違いますね.

つまり,オーバーロードにより見かけ上一つの関数として提供することができるのです!

例えば,下のようなsin(1)からsin(100)のうち正の数のみの和を求めるコードがあったとします.特に意味はありません.

let a = Array(0 ..< 100)

.map { sin(Double($0)) }
.filter { $0 > 0 }
.reduce (0.0) { $0 + $1 }

Objective-Cに比べれば格段にスマートな感じで書けるわけですが,関数名の文字数が合っていなくて美しくないですし,まだ冗長に感じないでしょうか.

そこで,このようにSequenceTypeを拡張してあげます:

extension SequenceType {

typealias Element = Self.Generator.Element

func $<T>(@noescape transform: Element throws -> T) rethrows -> [T] {
return try self.map(transform)
}

func $(@noescape predicate: Element throws -> Bool) rethrows -> [Element] {
return try self.filter(predicate)
}

func $<T>(initial: T, @noescape combine: (T, Element) throws -> T) rethrows -> T {
return try self.reduce(initial, combine: combine)
}

}

すると,以下のように略記することができるようになります!

let a = Array(0 ..< 100)

.$ { sin(Double($0)) }
.$ { $0 > 0 }
.$ (0.0) { $0 + $1 }


まとめ

読みにくいですし,型推論に時間がかかりすぎるので使わない方がいいと思います.

標準の型を拡張すると初見の可読性が下がる一方で,思わぬ便利な使い方ができることもあるので,バランスをとりつつ積極的にextension書いていきましょう!

あるいはSwiftもFoundationもオープンソース化されたことですし,良いextensionが書けたらプルリク を送って標準ライブラリにしてしまうのも手かもしれませんね.