とらのメモ

IT関係やガジェットについての雑記

ジェネリクスと向き合った

ジェネリクスgenerics)とは、型をパラメータとしてプログラムを記述するための機能です

いつなぜ使うのか

  • 汎用性の高いコード、関数名だけが異なり重複した処理を行っているコードの型の安全性を保証するため。
    • Any型にすることで全ての型を受け入れることもできるが、 エラーが発生する可能性が高い。

例えば... aとbを足して合計が100より小さいか確認する関数

func hanteiFuncInt(_ a: Int, _ b: Int)-> Bool {
    let total = a + b
    var hantei = total < 100
    return hantei
}

この場合、引数はInt型でないと実行することができない 引数をAny型にすることでも通るが、型の安全性が保証されずString型などを受け取った時にエラーが起きる

// Example
func genericHanteiFunc<T>(_ a: inout T, _ b: inout T)-> Bool where T: Comparable & Numeric {
    print(type(of: T.self))
    let total = a + b
    var hantei = total < 100
    return hantei
}

引数の型を比較可能な演算子が使えるものをジェネリクス関数として作成することで、 引数がInt, Float, Double, TimeIntervalなどであっても、エラーを起こさずに結果を返すようになり、不適切な引数の型はコードの時点で弾かれるようになる

ジェネリクスの例

func max<T>(_x:T,_y:T) -> T where T:Comparable

whereT:Comparable これは型TはプロトコルComparableに適合していなければならないという制約 固有の順序を持つ大小比較が可能なも型に適用される

Swiftでは、型Tの配列を[T]と書き、 キーがKeyType型、値がValueType型の辞書型を[KeyType:ValueType]と書く。

これらはプログラムを読み書きしやすくするために用意された記述法で、 パラメータ付き型指定を使えば、 Array<T>Dictionary<KeyType,ValueType> のように書くことができる。

// Example
func mySwap(_a:inout Int,_b:inout Int){
    let t=a; a=b; b=t 
}
// Int型に対してのみ有効なmySwap関数
func mySwap<T>(_ a:inout T, _ b:inout T){
    //ジェネリック関数
    let t=a; a=b; b=t
}

型パラメータ

<T>:関数定義、型定義に書くことによって、それ以下の定義でTを型パラメータとして使うことを示します。Tに対する条件はない。 <T,U>:型パラメータが2つ必要な場合はこのように記述する。 <T:OtherType>:型パラメータTは、プロトコルOtherTypeに適合するか、 クラスOtherType自体かそのサブクラスであることが条件となる。


where条件:先行する型パラメータに何らかの条件を付けます。この形の制約条件は、関数の引数列および(存在すれば)返り値の型の宣言の後、関数本体のコードブロックの前に記述します。 条件は以下のようなものです。 複数の条件をカンマ「,」で区切って並べ、AND条件を表すこともできます。

  • T:OtherType:型パラメータTがプロトコルOtherTypeに適合するか、クラスOtherType自体か、あるいはそのサブクラスであることを条件とします。
    • Comparable : 大小比較できる
    • Hashble : ハッシュ値が使える
    • Countable : countができる
  • T==U:型パラメータT(または型T)は、型パラメータU(または型U)と同じであることを条件とします。

// Example
func 名前 <T: プロトコル>(仮引数列) -> 返り値の型 where 条件 { 本体 }
subscript<T: プロトコル>(仮引数列) -> 返り値の型 where 条件 { 本体 }
struct 名前 where <T: プロトコル> where 条件 { 本体 }
extension 名前 where 条件 { 本体 }

拡張定義の条件に使う

struct PickForever<T>{ //要素を繰り返し取り出し続ける
    var list:[T]
    private var index = 0
    init<S>(_ a: S) where S: Sequence, T == S.Element {
        list = [T](a)      //初期値はシークエンスで指定
    }
    mutatingfuncpick()-> T { //要素を順番に取り出す
        if index >= list.count { index=0 }  //末尾を過ぎたら先頭に戻る
        let rtn = list[index]
        index += 1 

        return rtn
    }
}

extension PickForever where T:Comparable {//大小比較できる
    mutating func sort(){
        list.sort()
    }//昇順にソートする
}

extension PickForever where T:Hashable{   //ハッシュ値が使える
    mutating func makeUnique(){ 
        let tmp = Set<T>(list)  //集合にして重複要素を削除する
        list=[T](tmp)  //配列に戻すが、元の順序ではない
    }
} 

参考

荻原 剛志. 詳解 Swift 第5版 (Japanese Edition)