Swift 2で提唱されているProtocol Oriented ProgrammingをWWDCセッションから学ぶ


最近、Swift 2で提唱されているProtocol Oriented Programmingの理解がちょっと自信なくて、色々記事見つつも今いち掴めた気がしなかったので、原典であろうWWDCでそれを提唱していたセッションを見たところ、かなりすっきりした気がします。
セッション: [Protocol-Oriented Programming in Swift - WWDC 2015 - Videos - Apple Developer](https://developer.apple.com/videos/wwdc/2015/?id=408)

セッション冒頭は、クラス vs Protocolの話で知りたいこととちょっとズレていたのでそれはスキップしてます。
そのあたりは、[Building Better Apps with Value Types in Swift - WWDC 2015 - Videos - Apple Developer](https://developer.apple.com/videos/wwdc/2015/?id=414) の方が手厚そうなので、近々同じような感じで理解深めておきたいと思っています。

# まずはイケてないコード

```swift
class Ordered {
    func precedes(other: Ordered) -> Bool { fatalError("implement me!") } // 1
}

class Number: Ordered {
    var value: Double = 0
    override func precedes(other: Ordered) -> Bool {
        return value < (other as! Number).value // 2
    }
}
```

## 1. `fatalError("implement me!")`がイケてない

特にSwiftは抽象クラスが無いので、こういう基底クラス的なものを作ると、イケてなさが目立ちますね。

## 2. `other as! Number`でダウンキャストしている。

型の恩恵を殺しています(´・︵・｀)


セッションでは、"code smell"という表現で、このコードのイケてなさが表現されています。

# 改善後

```swift
protocol Ordered { // 1
    func precedes(other: Self) -> Bool // 2
}

struct Number: Ordered { // 3
    var value: Double = 0
    func precedes(other: Number) -> Bool { // 1
        return value < other.value // 2
    }
}
```

## 1. classからprotocolに変更

実装を抜くことが可能になります。
さらにNumberでのメソッド定義も、overrideは不要となります。

## 2. 受け取る型を`Self`に

Numberの実装で、Number型で受けられるようになり、ダウンキャストが無くなりました。

## 3. structに変更

これは明確な理由が述べられて無さそうに聞こえました。
冒頭で、参照型だとバグが生まれやすいなどの理由で、値型を推していたのでその一貫な気がします。

# バイナリサーチ関数に使う例

```swift
func binarySearch(sortedKeys: [Ordered], forKey k: Ordered) -> Int { // 1
    var lo = 0
    var hi = sortedKeys.count
    while hi > lo {
        let mid = lo + (hi - lo) / 2
        if sortedKeys[mid].precedes(k) { // 2
            lo = mid + 1
        } else {
            hi = mid
        }
    }
    return lo
}
```

## コンパイルエラー発生

一見良い例に見えますが、まだこのままではコンパイルエラーが発生します。

- `1: error: protocol 'Ordered' can only be used as a generic constraint because it has Self or associated type requirements`
- `2: error: member 'precedes' cannot be used on value of protocol type 'Ordered'; use a generic constraint instead`

見ての通り、OrderedプロトコルのprecedesメソッドがSelfを受けることが原因とのことです。
なぜ、これがダメなのかというと、Orderedは、Number以外もあり得るからです。

```swift
struct Label: Ordered {
    var value = ""
    func precedes(other: Label) -> Bool {
        return value < other.value
    }
}
```

こういった、Labelというstructがあった時、それがsortedKeysに渡されると、LabelとNumberを比較することになってしまいます。
これは解決不能です。

## ジェネリクスで解決

つまり、Orderedプロトコルを実装しているものであれば、何でも良いが、ただしそれは全て同じ型(セッションではhomogeniousと表現されている)である必要があります。

これは、このようにジェネリクスで表現出来ます。無事コンパイルエラーが解決しました(　´･‿･｀)

```swift
func binarySearch<T: Ordered>(sortedKeys: [T], forKey k: T) -> Int {
    var lo = 0
    var hi = sortedKeys.count
    while hi > lo {
        let mid = lo + (hi - lo) / 2
        if sortedKeys[mid].precedes(k) {
            lo = mid + 1
        } else {
            hi = mid
        }
    }
    return lo
}
```

## プロトコルで定義したメソッドの引数の型をSelfにするかどうかの違い

今のコンパイルエラーは、SelfにせずOrderedという具体的な型を指定していたら発生しませんでした。
それが良いことかという言うと逆で、型が入り交じった場合ダウンキャストのところで実行時エラーが発生します。
`Self`指定としたことでそれをコンパイルエラーによって未然に防げるようになったわけです。

セッションのこの表がとても分かりやすいです。

![Screen Shot 2015-09-19 at 4.40.49 PM.png](https://qiita-image-store.s3.amazonaws.com/0/19398/a3d11888-21ab-9121-e564-0a641a0c7bf7.png "Screen Shot 2015-09-19 at 4.40.49 PM.png")


# 図形を描くコードを例にとった説明

## まず初めのコード

```swift
// 図形などの定義
struct Renderer {
    func moveTo(p: CGPoint) { print("moveTo(\(p.x), \(p.y))") }
    func lineTo(p: CGPoint) { print("lineTo(\(p.x), \(p.y))") }
    func arcAt(center: CGPoint, radius: CGFloat, startAngle: CGFloat, endAngle: CGFloat) {
        print("arcAt(\(center), radius: \(radius), startAngle: \(startAngle), endAngle: \(endAngle))")
    }
}

protocol Drawable {
    func draw(renderer: Renderer)
}

struct Polygon: Drawable {
    func draw(renderer: Renderer) {
        renderer.moveTo(corners.last!)
        for p in corners {
            renderer.lineTo(p)
        }
    }
    var corners: [CGPoint] = []
}

struct Circle: Drawable {
    func draw(renderer: Renderer) {
        renderer.arcAt(center, radius: radius, startAngle: 0, endAngle: CGFloat(M_PI * 2))
    }
    var center: CGPoint
    var radius: CGFloat
}

struct Diagram: Drawable {
    func draw(renderer: Renderer) {
        for f in elements {
            f.draw(renderer)
        }
    }
    var elements: [Drawable] = []
}

// テストコード
var circle = Circle(center: CGPoint(x: 187.5, y: 333.5), radius: 93.75)

var triangle = Polygon(corners: [
    CGPoint(x: 187.5, y: 427.25),
    CGPoint(x: 268.69, y: 286.625),
    CGPoint(x: 106.31, y: 286.625)
    ])

var diagram = Diagram(elements: [circle, triangle])

diagram.draw(Renderer())
```

こういうログが吐かれて、円と三角形が描かれているようなことが何となく分かる

```
arcAt((187.5, 333.5), radius: 93.75, startAngle: 0.0, endAngle: 6.28318530717959)
moveTo(106.31, 286.625)
lineTo(187.5, 427.25)
lineTo(268.69, 286.625)
lineTo(106.31, 286.625)
```

## 実際の描画が見たい、と言われて変更

というわけで変更してみましょう。
まずは、Rendererをprotocolに変えて実装を削って、代わりにTestRendererに移しましょう。

```swift
protocol Renderer {
    func moveTo(p: CGPoint)
    func lineTo(p: CGPoint)
    func arcAt(center: CGPoint, radius: CGFloat, startAngle: CGFloat, endAngle: CGFloat)
}

struct TestRenderer: Renderer {
    func moveTo(p: CGPoint) { print("moveTo(\(p.x), \(p.y))") }
    func lineTo(p: CGPoint) { print("lineTo(\(p.x), \(p.y))") }
    func arcAt(center: CGPoint, radius: CGFloat, startAngle: CGFloat, endAngle: CGFloat) {
        print("arcAt(\(center), radius: \(radius), startAngle: \(startAngle), endAngle: \(endAngle))")
    }
}
```

さらに、実行部分はこうすると、とりあえず先ほどの挙動は維持出来ました。

```swift
diagram.draw(TestRenderer())
```

さらに、実際に描画してみましょう。
Rendererを実装したCGContextを定義するだけで簡単に出来てしまいます。
protocol orientedの強力さが分かった気がします(　´･‿･｀)

```swift
extension CGContext: Renderer {
    func moveTo(p: CGPoint) {
        CGContextMoveToPoint(self, p.x, p.y)
    }
    func lineTo(p: CGPoint) {
        CGContextAddLineToPoint(self, p.x, p.y)
    }
    func arcAt(center: CGPoint, radius: CGFloat, startAngle: CGFloat, endAngle: CGFloat) {
        let arc = CGPathCreateMutable()
        CGPathAddArc(arc, nil, center.x, center.y, radius, startAngle, endAngle, true)
        CGContextAddPath(self, arc)
    }
}
```

実行はこうです。

```
showCoreGraphicsDiagram("title", size: CGSizeMake(400, 600)) { diagram.draw($0) }
```

PlaygroundでのCGContext描画のために、 https://developer.apple.com/sample-code/wwdc/2015/downloads/Crustacean.zip のヘルパーメソッドも必要です。

# 他のの図形を表現可能とするためにRendererを拡張

## 指定箇所に円を描く`circleAt`の定義を足します。

```swift
protocol Renderer {
    func moveTo(p: CGPoint)
    func lineTo(p: CGPoint)
    func arcAt(center: CGPoint, radius: CGFloat, startAngle: CGFloat, endAngle: CGFloat)
    func circleAt(center: CGPoint, radius: CGFloat)
}

struct TestRenderer: Renderer {
    func moveTo(p: CGPoint) { print("moveTo(\(p.x), \(p.y))") }
    func lineTo(p: CGPoint) { print("lineTo(\(p.x), \(p.y))") }
    func arcAt(center: CGPoint, radius: CGFloat, startAngle: CGFloat, endAngle: CGFloat) {
        print("arcAt(\(center), radius: \(radius), startAngle: \(startAngle), endAngle: \(endAngle))")
    }
    func circleAt(center: CGPoint, radius: CGFloat) {
        arcAt(center, radius: radius, startAngle: 0, endAngle: CGFloat(M_PI * 2))
    }
}

extension CGContext: Renderer {
    func moveTo(p: CGPoint) {
        CGContextMoveToPoint(self, p.x, p.y)
    }
    func lineTo(p: CGPoint) {
        CGContextAddLineToPoint(self, p.x, p.y)
    }
    func arcAt(center: CGPoint, radius: CGFloat, startAngle: CGFloat, endAngle: CGFloat) {
        let arc = CGPathCreateMutable()
        CGPathAddArc(arc, nil, center.x, center.y, radius, startAngle, endAngle, true)
        CGContextAddPath(self, arc)
    }
    func circleAt(center: CGPoint, radius: CGFloat) {
        arcAt(center, radius: radius, startAngle: 0, endAngle: CGFloat(M_PI * 2))
    }
}
```

`TestRenderer`と`CGContext`に、`circleAt`のコピペ実装が発生してしまいました(´・︵・｀)

## ここでSwift 2の`Protocol Extensions`

このようにRenderを拡張して`circleAt`を実装しましょう。

```swift
protocol Renderer {
    func moveTo(p: CGPoint)
    func lineTo(p: CGPoint)
    func arcAt(center: CGPoint, radius: CGFloat, startAngle: CGFloat, endAngle: CGFloat)
    func circleAt(center: CGPoint, radius: CGFloat)
}

extension Renderer {
    func circleAt(center: CGPoint, radius: CGFloat) {
        arcAt(center, radius: radius, startAngle: 0, endAngle: CGFloat(M_PI * 2))
    }
}
```

`TestRenderer`と`CGContext`に、`circleAt`のコピペ実装は削除しましょうʕ ·ᴥ·ʔ
すっきりしました(　´･‿･｀)

Swiftは抽象クラス無いですが、それを`protocol`と`protocol extension`の組み合わせですっきり実現されていると、僕は感じています。
抽象クラスがある言語だと、Interface使うか抽象クラス使うべきか迷う、みたいな声を良く聞きますが、Swiftだと

- Protocolでインターフェースを定義
- 必要ならProtocolを拡張してデフォルト実装を埋め込む

という感じですっきり解決出来ます。
そこの役割が言語レベルで明確に分けられているのが筋が良いと思います。

# CollectionTypeのindexOfメソッドを実装する例

## コンパイルエラーになってしまうもの

```swift
extension CollectionType {
    public func indexOf(element: Generator.Element) -> Index? {
        for i in indices {
            if self[i] == element { // エラー
                return i
            }
        }
        return nil
    }
}
```

`error: binary operator '==' cannot be applied to two 'Self.Generator.Element' operands`というエラーが発生してしまいました(´・︵・｀)

## 解決(　´･‿･｀)

`where Generator.Element: Equatable`の拘束条件を付けることで、`Generator.Element`が`Equatable`(`==`評価可能なもの)に限定でき、上記のコンパイルエラーが解決します。

```swift
extension CollectionType where Generator.Element: Equatable {
    public func indexOf(element: Generator.Element) -> Index? {
        for i in indices {
            if self[i] == element { 
                return i
            }
        }
        return nil
    }
}
```

# バイナリサーチの例に戻ってProtocol Extensionsを活用

## `binarySearch`を呼んでみる

```swift
let position = binarySearch([1, 2, 3, 4], forKey: 2)
```

`expected an argument list of type '([T], forKey: T)'`のエラーが出ます。
IntはOrderedプロトコルに従ってないので、仕方ないですね(´・︵・｀)

Intを拡張すれば解決します(　´･‿･｀)

```swift

extension Int: Ordered {
    func precedes(other: Int) -> Bool {
        return self < other
    }
}
```

この勢いで`String`も拡張しちゃいましょう(　´･‿･｀)

```swift
extension String: Ordered {
    func precedes(other: String) -> Bool {
        return self < other
    }
}

let position = binarySearch(["1", "2", "3", "4"], forKey: "2")
```

なんか、イヤな感じがしてきましたね(´・︵・｀)

## 重複実装を無くしましょう

IntやStringはComparableを実装しているので、このように定義すれば、Int・StringはOrderedにさえ従っていれば良いです。
かなりマシになりましたね。

```swift
extension Comparable {
    func precedes(other: Self) -> Bool {
        return self < other
    }
}
extension Int: Ordered {}
extension String: Ordered {}
```

### Orderedに従う必要ないのでは？

そもそも、`Comparable`に従っているものが`func precedes(other: Self) -> Bool`を実装していることが宣言されているならば、`extension Int: Ordered {}`などの記述は冗長なのでは？とも思ってきます。
しかし、`extension Int: Ordered {}`を削るとコンパイルエラーになります。

```swift
error: cannot invoke 'binarySearch' with an argument list of type '([Int], forKey: Int)'
```

Swiftは、構造的部分型(ダックタイピングに近いけどコンパイル時に確定するものみたいな感じ)の発想は取り入れていないということだと思います。

## ちぐはぐな挙動を改善

例えば、DoubleもComparableを実装しているので、これはコンパイルが通ります。

```swift
let truth = 3.14.precedes(98.6)
```

一方、こちらは

```swift
let position = binarySearch([1.0, 2.0, 3.0, 4.0], forKey: 2.0)
```

`error: cannot invoke 'binarySearch' with an argument list of type '([Double], forKey: Double)'`というコンパイルエラーになります。

そもそもDoubleに`precedes`メソッドを生やす気が無いなら、Comparableの拡張を限定的にしましょう。

このように定義すれば、`let truth = 3.14.precedes(98.6)`もコンパイルエラーになり、Doubleに意図しないメソッドを生やさずに全体的に整合感のある挙動になります。

```swift
extension Ordered where Self: Comparable {
    func precedes(other: Self) -> Bool {
        return self < other
    }
}
```

## より厳密な指定に

普通にジェネリクスで厳密に型を指定するとこうなるようです。

```swift
func binarySearch<
    C: CollectionType where C.Index == RandomAccessIndexType,
    C.Generator.Element: Ordered
    >(sortedKeys: C, forKey k: C.Generator.Element) -> Int {
    // ...
}
```

かなり見にくいですが、Protocol Extensionを活用すると、少し目に優しくなります(　´･‿･｀)

```swift
extension CollectionType where Index == RandomAccessIndexType,
Generator.Element: Ordered {
    func binarySearch(forKey k: C.Generator.Element) -> Int {
        // ...
    }
}
// 呼び出しもメソッドライクに(　´･‿･｀)
let position = [1, 2, 3, 4].binarySearch(2)
```

かなり"Protocol Oriented"な雰囲気がしてきました(　´･‿･｀)

※: おそらくWWDCの時からの仕様変更で`RandomAccessIndexType`部分でコンパイルエラー(`error: same-type constraint type 'RandomAccessIndexType' does not conform to required protocol 'ForwardIndexType'`)が発生します。原因分かったら反映します。

# プロトコルを実装するとたくさんのデフォルト実装がくっついてくる

例えば、このようにOptionalSetTypeを実装すると、こういうSet系のメソッドが付いてきます。
これらはProtocol Extensionsで実装されたメソッド群です。
Swiftの標準ライブラリのようにうまく設計すると最小手数で既存実装の恩恵得られるようになるということです。

![Screen Shot 2015-09-19 at 8.09.04 PM.png](https://qiita-image-store.s3.amazonaws.com/0/19398/9730f31b-729a-d77c-49bf-55b33e15c91d.png "Screen Shot 2015-09-19 at 8.09.04 PM.png")

ちなみに、OptionSetTypeはこういう定義ですが、RawRepresentableで定義されたデフォルト定義の無い`public var rawValue: Self.RawValue { get }`を満たすために、上のような実装となっているようです。

```
public protocol OptionSetType : SetAlgebraType, RawRepresentable {
    /// An `OptionSet`'s `Element` type is normally `Self`.
    typealias Element = Self
    /// Convert from a value of `RawValue`, succeeding unconditionally.
    public init(rawValue: Self.RawValue)
}
```


# Equatableサポート

この後、[図形を描くコードを例にとった説明](http://qiita.com/mono0926/items/bb52e657df6978713334#図形を描くコードを例にとった説明)で定義したstructの等価比較についての話になりましたが、ここはなかなか難しい + ボリューミーでややこしいので、ちょっと力尽きてしまいました。
[プロトコルで定義したメソッドの引数の型をSelfにするかどうかの違い](http://qiita.com/mono0926/items/bb52e657df6978713334#プロトコルで定義したメソッドの引数の型をselfにするかどうかの違い)で書いた内容に関連があるのですが、型が厳密なのでイコール比較の際にそれを理解した上でちょっとした回避策を取る必要のある場面がある、というような内容です。

追記するかもしれませんが、とりあえずセッションご覧下さい。

---

# 関連

- [Protocol-Oriented Programming in Swift](http://www.infoq.com/news/2015/06/protocol-oriented-swift)
    - この記事書いた後に見つけましたが、ほぼ同内容の英語記事です。