LoginSignup
119
117

More than 5 years have passed since last update.

Swift 2のProtocol-Oriented Programmingっぽい書き方を理解する

Last updated at Posted at 2016-01-27

ああそれ古い。

SwiftのFunctional Programmingっぽい書き方を理解する

swift1-way
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...10Arrayにしているところ。もうしなくていいんです。そう、Swift 2ならね。

実際、Swift 2では.mapはもうArrayでは定義されてないんです。公式ドキュメントのArray Structure Referenceを見ても、.mapはどこにも見当たりません。

ではどこで定義されているか?

Arrayが準拠しているprotocol CollectionType です。実際にCollectionType Protocol Referenceを見てみると、.map.filterなどの定義がごっそりと見当たります。またCollectionTypeには、Arrayのみならず、(1...10)の型であるRangeDictionaryも準拠していることもわかります。

そしてこのprotocolが継承している(親)protocolであるSequenceTypeにも同様の定義があることがSequenceType Protocol Referenceから読み取れます。たとえば.reduceCollectionTypeではなくSequenceTypeに定義があります。

ということはDictionary.mapできるとこと?

できちゃうんですね、これが。

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も同様の挙動を示します。

これが何を意味するか?

  • SequenceTypeCollectionTypeに準拠した型であれば、.map.filterは無料で手に入るんです!

  • そしてSequeceTypeCollectionTypeにメソッドを追加したら、それに準拠した型にも無料で新メソッドを提供できるんです!

ほんとに?

やりましょう!

CollectionType#asDictionary
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]

うまく行ったようです。ArrayRangeも拡張してないのに、この通り。

とはいえ、現状では問題もいくつか。

  • [Generator.Element:Generator.Element]()がなぜかエラーを返す。のでDictionary<Generator.Element,Generator.Element>()
  • 抽象化されすぎててわかりづらい。例えばidxIntではなく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を拡張しておくことで対処できます。

Dictionary#asDictionary
extension Dictionary {
    var asDictionary:[Key:Value] {
        return self
    }
}

各型(types = struct, enum, class)の実装はprotocol extensionより優先されるので。

課題は残れど、Protocol Extensionのおかげで継承のできないstructenumでも、OOPにおける親クラスの拡張に相当することが出来るようになったのは画期的です。Protocol-Oriented Programming = POPをAppleが提唱しはじめるぐらいに。WWDC2015のProtocol-Oriented Programming in Swiftは必見です。

実際、Swift Standard Library Referenceを見てみると、class
の外様扱いぶりに驚愕します。SwiftにとってclassはあくまでObjective-Cの遺産継承のためにあって、structenumprotocolでつないでいくPOPこそがSwift流と言わんがばかりに。

実際のところ、Protocol Extensionを書くのは通常(つまりclassstructenum)のExtensionよりは気を使いますし、知らなくても動くコードは書けるのですが、少なくとも Swift は Protocol-Oriented Programming Language であるということを知らないと、調べものをするときに困るわけです。

このあたりは、Software Design誌の連載の次号記事に書いたのでよろしければ一読を、とステマで締めさせていただきます:-)

Dan the Crusty Swift Programmer

119
117
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
119
117