ああそれ古い。
SwiftのFunctional Programmingっぽい書き方を理解する
func isEven(number: Int) -> Bool {
return number % 2 == 0
}
let evens = Array(1...10).filter(isEven)
print(evens) // [0, 2, 4, 6, 8, 10]
何が古いかというと、いちいち1...10をArrayにしているところ。もうしなくていいんです。そう、Swift 2ならね。
実際、Swift 2では.mapはもうArrayでは定義されてないんです。公式ドキュメントのArray Structure Referenceを見ても、.mapはどこにも見当たりません。
ではどこで定義されているか?
Arrayが準拠しているprotocol CollectionType です。実際にCollectionType Protocol Referenceを見てみると、.mapや.filterなどの定義がごっそりと見当たります。またCollectionTypeには、Arrayのみならず、(1...10)の型であるRangeやDictionaryも準拠していることもわかります。
そしてこのprotocolが継承している(親)protocolであるSequenceTypeにも同様の定義があることがSequenceType Protocol Referenceから読み取れます。たとえば.reduceはCollectionTypeではなくSequenceTypeに定義があります。
ということはDictionaryも.mapできるとこと?
できちゃうんですね、これが。
let dict = ["SWIFT":2.0,"XCODE":7.0].map {
($0.0.lowercaseString, $0.1 + 0.1)
}
// [("swift", 2.1), ("xcode", 7.1)]
ただし、見ての通り結果は[String:Double]ではなく[(String,Double)]とArrayになってます。余談ですがrubyのHashも同様の挙動を示します。
これが何を意味するか?
-
SequenceTypeやCollectionTypeに準拠した型であれば、.mapや.filterは無料で手に入るんです! -
そして
SequeceTypeやCollectionTypeにメソッドを追加したら、それに準拠した型にも無料で新メソッドを提供できるんです!
ほんとに?
やりましょう!
extension CollectionType where Generator.Element:Hashable {
var asDictionary:[Generator.Element:Generator.Element] {
// var result = [Generator.Element:Generator.Element]()
// causes 'ambiguous reference to member "Generator"'
var result = Dictionary<Generator.Element,Generator.Element>()
var even = true;
for idx in self.indices {
if even {
result[self[idx]] = self[idx.successor()]
}
even = !even
}
return result
}
}
Perl のmy %hash = @arrayに相当するものですが、うまくいくでしょうか?
[1,2,3,4,5,6].asDictionary // [5: 6, 3: 4, 1: 2]
(1...6).asDictionary // [5: 6, 3: 4, 1: 2]
うまく行ったようです。ArrayもRangeも拡張してないのに、この通り。
とはいえ、現状では問題もいくつか。
-
[Generator.Element:Generator.Element]()がなぜかエラーを返す。のでDictionary<Generator.Element,Generator.Element>() - 抽象化されすぎててわかりづらい。例えば
idxはIntではなくSelf.Indexなので、idx+1とかは使えずidx.successor()としなければならない。 -
[1:2,3:4,5:6].asDictionaryは期待どおりエラーとなるが、これまたエラーメッセージが"type of expression is ambiguous without more context"と的外れ。(Key,Value)がHashableでないのが本当の理由なのに。
とはいえ、最後の問題はDictionaryを拡張しておくことで対処できます。
extension Dictionary {
var asDictionary:[Key:Value] {
return self
}
}
各型(types = struct, enum, class)の実装はprotocol extensionより優先されるので。
課題は残れど、Protocol Extensionのおかげで継承のできないstructやenumでも、OOPにおける親クラスの拡張に相当することが出来るようになったのは画期的です。Protocol-Oriented Programming = POPをAppleが提唱しはじめるぐらいに。WWDC2015のProtocol-Oriented Programming in Swiftは必見です。
実際、Swift Standard Library Referenceを見てみると、class
の外様扱いぶりに驚愕します。SwiftにとってclassはあくまでObjective-Cの遺産継承のためにあって、structやenumをprotocolでつないでいくPOPこそがSwift流と言わんがばかりに。
実際のところ、Protocol Extensionを書くのは通常(つまりclassやstructやenum)のExtensionよりは気を使いますし、知らなくても動くコードは書けるのですが、少なくとも Swift は Protocol-Oriented Programming Language であるということを知らないと、調べものをするときに困るわけです。
このあたりは、Software Design誌の連載の次号記事に書いたのでよろしければ一読を、とステマで締めさせていただきます:-)
Dan the Crusty Swift Programmer
