Swiftでジェネリクスを使った構造体/クラスを実装してみた


## はじめに

Swiftはジェネリクスをサポートしており、配列や辞書を作る際に型を指定できるようになりました。
こういう有り物を使う側としてはとっても便利というイメージがあるジェネリクスですが、いざ自分でこういったモノを作ろうと思ったらいろいろハマってしまったので、実装中に出くわしたエラーと、それをどうやって乗り切ったかを記録として残しておきます。今後ジェネリクスを使う方のヒントにでもなれば幸いです。


* Xcode 6.0.1
* Playground
* 私はジェネリクス初心者です

## 動機

`CGPoint`型のx,y要素は`CGFloat`型となっており、`CGFloat`型は32ビットCPU環境では32ビット長(Float相当)、64ビットCPU環境では64ビット長(Double相当)という仕様になっています。

で、これがちょっと困る場合があるため、
**「CPU依存しないPoint型（2D座標型）が作りたい」**
**「ついでに整数型にも対応したい」**

...と思ったのです（本当に使うかは知らない）。

つまり、`PointI`型(`Int32`型)、`PointF`型(`Float`型)、`PointD`型(`Double`型)　等々を作りたいのですが、それなら
**ジェネリクス使えば一気にできるんじゃね？**と。

## とりあえずベースとなる部分を作ってみた

`PointT`というジェネリクスを使った構造体を作り、（構造体の派生は出来ないので）`typealias`で`PointI`などを定義します。

```swift:
struct PointT<T> {
    var x: T
    var y: T
    
    init(_ x: T, _ y: T) {
        self.x = x
        self.y = y
    }
}

// 各種型を定義
typealias PointI = PointT<Int32>
typealias PointF = PointT<Float>
typealias PointD = PointT<Double>

// テスト
var i = PointI(0, 0) // {x 0, y 0}
var f = PointF(1.5, 1.5) // {x 1.5, y 1.5}
var d = PointD(2.2, 2.2) // {x 2.2, y 2.2}
```

たぶんここまでは誰でも書けると思われます。

## offset関数を実装してみる

2D座標クラスにはありがちの`offset`関数（座標を指定した分移動させる関数）を実装してみます。

で、よくわからないエラーが出て怒られます。
（どこから`UInt8`が出てきたのでしょう??）

![スクリーンショット 2014-10-01 10.50.41.png](https://qiita-image-store.s3.amazonaws.com/0/31347/bbb79563-0f0d-027a-a89a-104a8ea85396.png)

たぶん、「`'T'`型が足し算できるなんてこっちは知らんから」という理由でエラーが出ていると予想されます。

こうなった場合は、'T'型に制約を付けて、足し算(`+=`)できる型のみ受け付けるようにすれば良いんですよね。

**たぶん、四則演算用のプロトコルがすでに定義されてるはず...**

**...無いし?**

というわけで、自分で`PointTElement`というプロトコルを定義し、それを制約にしました。

```swift:

protocol PointTElement {
    func +=(inout lhs: Self, rhs: Self)
}

struct PointT<T: PointTElement> {
    var x: T
    var y: T
    
    init(_ x: T, _ y: T) {
        self.x = x
        self.y = y
    }
    
    mutating func offset(cx: T, _ cy: T) {
        x += cx
        y += cy
    }
}

// 各種型を定義
typealias PointI = PointT<Int32>
extension Int32: PointTElement {
}

typealias PointF = PointT<Float>
extension Float: PointTElement {
}

typealias PointD = PointT<Double>
extension Double: PointTElement {
}

// テスト
var i = PointI(0, 0)
i.offset(5, 5)
i // {x 5, y 5}
var f = PointF(0, 0)
f.offset(5, 5)
f // {x 5.0, y 5.0}
var d = PointD(0, 0)
d.offset(5, 5)
d // {x 5.0, y 5.0}
```

`extension`で各型を`PointTElement`に準拠させるのがポイントでしょうか。
`+=`演算子自体は各型ですでに実装されているので、他にやることはありません。

ちなみに`func +=(inout lhs: Self, rhs: Self)`←こういった書き方はSwiftの定義ソースから適当に拝借してきて型だけ`Self`に置き換えています。

## 2点間の直線距離を求める関数を実装してみる（T型からDouble型へ変換）

こちらも2D座標クラスにはありがちのやつです。ピタゴラスの定理でしたっけ。

冗長かもしれませんが、一旦`Double`型にしてから計算するという方針を採ります。

とりあえず実装してみますが、真っ赤っかになります。

![スクリーンショット 2014-10-01 11.17.55.png](https://qiita-image-store.s3.amazonaws.com/0/31347/9b726161-7f0c-1d67-2f04-e1e47bc5706b.png)

たぶん、`Double`型のイニシャライザに`T`型なんていう謎の型が来られても知らないし！って事でエラーが出てるんだと思います。

ここはどう回避してよいのかわからなかったので、仕方なく`PointTElement`プロトコルに`Double`型変換用のプロパティを定義し、それを使うようにしました。

```swift:
import Darwin

protocol PointTElement {
    func +=(inout lhs: Self, rhs: Self)
    var doubleValue: Double { get } // これ
}

struct PointT<T: PointTElement> {
    var x: T
    var y: T
    
    init(_ x: T, _ y: T) {
        self.x = x
        self.y = y
    }
    
    mutating func offset(cx: T, _ cy: T) {
        x += cx
        y += cy
    }
    
    func distance(pt2: PointT<T>) -> Double {
        let cx = x.doubleValue - pt2.x.doubleValue
        let cy = y.doubleValue - pt2.y.doubleValue
        return sqrt(cx * cx + cy * cy)
    }
}

// 各種型を定義
typealias PointI = PointT<Int32>
extension Int32: PointTElement {
    var doubleValue: Double {
        return Double(self)
    }
}

typealias PointF = PointT<Float>
extension Float: PointTElement {
    var doubleValue: Double {
        return Double(self)
    }
}

typealias PointD = PointT<Double>
extension Double: PointTElement {
    var doubleValue: Double {
        return self
    }
}

// テスト
var i = PointI(0, 0).distance(PointI(5, 5)) // 7.07106781186548
var f = PointF(0, 0).distance(PointF(5, 5)) // 7.07106781186548
var d = PointD(0, 0).distance(PointD(5, 5)) // 7.07106781186548
```

## 逆にDouble型からT型へ変換する場合

ちょっと良い題材（関数）が思いつかなかったので急にこんな感じになっちゃいましたが、
`Double`型で計算した結果を`T`型（または`PointT`型）へ戻すような事をしたいケースもあるかと思います。

こういった場合は、プロトコルをいじり...

```swift:
protocol PointTElement {
    func +=(inout lhs: Self, rhs: Self)
    var doubleValue: Double { get }
    init(_ v: Double) // これ
}
```

...のように`Double`型を引数に持つイニシャライザを定義してあげれば良さそうです。
大抵の型は、すでにこのイニシャライザが実装されているので、他にやる事はありません。

```swift:使い方
struct PointT<T: PointTElement> {
    (中略)
    
    func hoge() -> PointT {
        let d: Double = 0
        return PointT(T(d), T(d)) // これ
    }
}
```

## CGPoint型へ変換する方法を用意してみる

`Double`型への変換と同じように`CGFloat`型への変換をプロトコルで定義するしかないのかなと。

```swift:
protocol PointTElement {
    func +=(inout lhs: Self, rhs: Self)
    var doubleValue: Double { get }
    var CGFloatValue: CGFloat { get } // これ
    init(_ v: Double)
}
```

（プロパティ名の頭文字が大文字になるのがちょっと気持ちよくないのが失敗した感あります）

あとは各型で定義し、`CGPoint`の変換用イニシャライザを用意してあげれば動きます。

```swift:
// 各種型を定義
typealias PointI = PointT<Int32>
extension Int32: PointTElement {
    var doubleValue: Double {
        return Double(self)
    }
    var CGFloatValue: CGFloat {
        return CGFloat(self)
    }
}

typealias PointF = PointT<Float>
extension Float: PointTElement {
    var doubleValue: Double {
        return Double(self)
    }
    var CGFloatValue: CGFloat {
        return CGFloat(self)
    }
}

typealias PointD = PointT<Double>
extension Double: PointTElement {
    var doubleValue: Double {
        return self
    }
    var CGFloatValue: CGFloat {
        return CGFloat(self)
    }
}

extension CGPoint {
    init<T>(_ pt: PointT<T>) {
        x = pt.x.CGFloatValue
        y = pt.y.CGFloatValue
    }
}

// テスト
var i = PointI(15, 20)
var j = CGPoint(i) // {x 15 y 20}
```

## 終わりに

いろいろな型をジェネリクスで吸収するという目的はそこそこ達成できたのではないかと思われます。
もしジェネリクスが得意な方で他に良い実装方法などをご存知の方がいらっしゃいまいしたら、教えていただければ幸いです。