ああそれ古い。
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