swapTwoValues(_:_:) 関数と Stack 型は、あらゆる型を扱うことができます。しかしながら、ジェネリック関数やジェネリック型で使用できる型に対して、型制約を強制することが有益な場合があります。型パラメータが特定のクラスから継承する、あるいは特定のプロトコルやプロトコルコンポジションに準拠する必要があることを、型制約で指定します。

例えば、Swift の Dictionary 型は辞書のキーとして使用できる型に制約を課しています。Dictionaries で説明しているように、辞書のキーの型は hashable である必要があります。つまり、それ自体をユニークに表せる手段を提供する必要があります。特定のキーの値がすでに含まれているかをチェックできるように、辞書のキーを hashable にする必要があります。この要件無くして、特定のキーに値を挿入または置換できるか、あるいはキーの値がすでに辞書にあるかを、Dictionary が見分けることはできません。

この要件は、Dictionary のキーの型に対する型制約によって強制され、キーの型が Swift 標準ライブラリで定義された特別なプロトコルである Hashable プロトコルに準拠する必要があると指定しています。Swift の(StringIntDoubleBool のような)基本型はすべて、デフォルトで hashable です。

ジェネリック型を生成するときに独自の型制約を定義することができ、ジェネリックプログラミングの力の大部分を制約がもたらします。Hashable のような抽象概念は、明確な型よりも、概念的な特徴の観点から型を特徴づけます。

型制約シンタックス

型パラメータの一部として、型パラメータの名前の後にコロンで区切って、クラスまたはプロトコルの制約を置いて型制約を記述します。ジェネリック関数の型制約の基本シンタックスを(ジェネリック型のシンタックスと同じですが)以下に示します。

func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
    // 関数本体
}

この仮の関数には 2 つの型パラメータがあります。はじめの型パラメータ T には、SomeClass のサブクラスである T を必要とするという型制約があります。次の型パラメータ U には、SomeProtocol プロトコルに準拠する U を必要とするという型制約があります。

型制約の動作

次はジェネリックでない関数 findStringIndex で、探す String 値と、その文字列を含む String 値の配列が与えられます。findStringIndex(_:_:) 関数は、文字列が見つかった場合には配列ではじめて一致した文字列のインデックスのオプショナル Int 値を返し、文字列が見つからなかった場合には nil を返します。

func findStringIndex(array: [String], _ valueToFind: String) -> Int? {
    for (index, value) in array.enumerate() {
        if value == valueToFind {
            return index
        }
    }
    return nil
}

findStringIndex(_:_:) を、文字列の配列内に文字列値があるかを見つけるために使用することができます。

let strings = ["cat", "dog", "llama", "parakeet", "terrapin"]
if let foundIndex = findStringIndex(strings, "llama") {
    print("The index of llama is \(foundIndex)")
}
// "The index of llama is 2" と出力

配列にある値のインデックスを見つける考え方は、文字列でだけ役立つわけではありません。文字列の部分をある型 T の値に置き換えて、同じ機能をジェネリック関数 findIndex として記述することができます。

次は、findStringIndex のジェネリックバージョン findIndex をどのように記述するかです。配列からのオプショナル値ではなく、インデックスのオプショナル数値を関数が返すため、この関数の戻り値の型は依然として Int? であることに注目してください。この例の後で説明する理由により、この関数はコンパイルできません。

func findIndex<T>(array: [T], _ valueToFind: T) -> Int? {
    for (index, value) in array.enumerate() {
        if value == valueToFind {
            return index
        }
    }
    return nil
}

上述のとおり、この関数はコンパイルできません。問題は等価チェック if value == valueToFind にあります。Swift のあらゆる型を演算子 ==(等しい)で比較できるわけではありません。例えば、複雑なデータモデルを表現する独自のクラスや構造体を作成した場合、そのクラスや構造体においての「等しい」の意味は、Swift が推測するものとは異なります。これにより、このコードがあらゆる型 T で動作することを保証することはできず、コードをコンパイルしようとしたときに、適切なエラーが報告されます。

手段が無いわけではありません。Swift 標準ライブラリは、型の 2 つの値を比較する == 演算子(等しい)や != 演算子(等しくない)を実装する準拠型を要求する、Equatable プロトコルを定義しています。Swift の標準型はすべて、自動的に Equatable プロトコルをサポートしています。

Equatable であるあらゆる型は、「等しい」演算子をサポートすることが保証されており、安全に findIndex(_:_:) で使用することができます。この事実を表現するために、関数を定義するときに型パラメータの定義の一部として Equatable の型制約を記述します。

func findIndex<T: Equatable>(array: [T], _ valueToFind: T) -> Int? {
    for (index, value) in array.enumerate() {
        if value == valueToFind {
            return index
        }
    }
    return nil
}

findIndex の型パラメータは、「Equatable プロトコルに準拠するあらゆる型 T」を意味する T: Equatable として記述されています。

findIndex(_:_:) 関数はコンパイルに成功し、Double や String のような Equatable であるあらゆる型を使用することができます。

let doubleIndex = findIndex([3.14159, 0.1, 0.25], 9.3)
// 9.3 は配列に無いため、doubleIndex は値が無いオプショナル Int
let stringIndex = findIndex(["Mike", "Malcolm", "Andrea"], "Andrea")
// stringIndex は 2 の値を含むオプショナル Int

Portions of this page are translations based on work created and shared by Apple and used according to terms described in the Creative Commons Attribution 4.0 International License.