web-dev-qa-db-ja.com

プロトコルに関連付けられた型がSwiftでジェネリック型構文を使用しないのはなぜですか?

一方ではプロトコルの関連する型に使用される構文と、他方ではジェネリック型の構文の違いについて混乱しています。

たとえば、Swiftでは、次のようなものを使用してジェネリック型を定義できます。

struct Stack<T> {
    var items = [T]()
    mutating func Push(item: T) {
        items.append(item)
    }
    mutating func pop() -> T {
        return items.removeLast()
    }
}

一方、次のようなものを使用して、関連するタイプを持つプロトコルを定義します

protocol Container {
    typealias T
    mutating func append(item: T)
    var count: Int { get }
    subscript(i: Int) -> T { get }
}

なぜ後者ではないのですか?

protocol Container<T> {
    mutating func append(item: T)
    var count: Int { get }
    subscript(i: Int) -> T { get }
}

言語が後者の構文を採用していないのには、いくつかの深い(またはおそらく明白で私に失われた)理由がありますか?

51
orome

これは、devlistで数回取り上げられました。基本的な答えは、関連する型は型パラメーターよりも柔軟性があるということです。ここでは1つのタイプのパラメーターの特定のケースがありますが、複数のタイプのパラメーターが存在する可能性があります。たとえば、コレクションには要素タイプがありますが、インデックスタイプとジェネレータタイプもあります。型のパラメータ化でそれらを完全に特殊化した場合、Array<String, Int, Generator<String>>などのことについて話す必要があります。 (これにより、機能と見なすことができるInt以外の添え字が付いた配列を作成できますが、複雑さが大幅に増します。)

すべてをスキップすることも可能ですが(Javaはそうします)、その場合、型を制約する方法が少なくなります。 Java実際には、型を制約する方法がかなり限られています。Javaのコレクションに任意のインデックス型を付けることはできません。Scala extends = Java Swiftのような関連する型を持つ型システム。関連する型はScalaで信じられないほど強力です。それらはまた、混乱と髪の引き裂きの定期的な原因でもあります。

この余分な力が価値があるかどうかは、まったく別の問題であり、時間だけが教えてくれます。ただし、関連付けられた型は、単純な型のパラメーター化よりも強力です。

28
Rob Napier

RobNapierの答えは(いつものように)非常に良いですが、さらに啓発的であるかもしれない別の視点のために...

関連する型について

プロトコルは、一連の抽象的な要件です。具体的なタイプがプロトコルに準拠していると言うために満たす必要があるチェックリストです。伝統的に、ビヘイビアのチェックリストについて考えています。具体的なタイプによって実装されるメソッドまたはプロパティです。関連付けられたタイプは、そのようなチェックリストに関係するものに名前を付け、それによってhowaに関して制限のないまま定義を拡張する方法です。適合タイプは適合を実装します。

あなたが見たとき:

_protocol SimpleSetType {
    associatedtype Element
    func insert(_ element: Element)
    func contains(_ element: Element) -> Bool
    // ...
}
_

つまり、型がSimpleSetTypeへの準拠を主張するには、その型にinsert(_:)関数とcontains(_:)関数が含まれている必要があるだけでなく、これら2つの関数は同じ型のパラメーターをそれぞれとる必要があります。その他。ただし、そのパラメーターのタイプが何であるかは関係ありません。

このプロトコルは、ジェネリック型または非ジェネリック型で実装できます。

_class BagOfBytes: SimpleSetType {
    func insert(_ byte: UInt8) { /*...*/ }
    func contains(_ byte: UInt8) -> Bool { /*...*/ }
}

struct SetOfEquatables<T: Equatable>: SimpleSetType {
    func insert(_ item: T) { /*...*/ }
    func contains(_ item: T) -> Bool { /*...*/ }
}    
_

BagOfBytesまたはSetOfEquatablesは、_SimpleSetType.Element_と2つのメソッドのパラメーターとして使用される型との間の接続をどこにも定義しないことに注意してください。関連するタイプ。

ジェネリック型パラメーターについて

関連する型が抽象チェックリストを作成するための語彙を拡張する場合、ジェネリック型パラメーターは具象型の実装を制限します。次のようなジェネリッククラスがある場合:

_class ViewController<V: View> {
    var view: V
}
_

ViewControllerを作成する方法はたくさんあるとは言わない(viewがある限り)、ViewControllerは実際の具体的なものであると言います、およびviewがあります。さらに、特定のViewControllerインスタンスのビューの種類は正確にはわかりませんが、ViewViewクラスのサブクラスか、Viewプロトコルを実装する型のいずれか)である必要があります...言わないでください)。

別の言い方をすれば、ジェネリック型またはジェネリック関数を記述することは、実際のコードを記述するためのショートカットのようなものです。この例を見てみましょう:

_func allEqual<T: Equatable>(a: T, b: T, c: T) {
    return a == b && b == c
}
_

これは、Equatable型をすべて調べて次のように書いた場合と同じ効果があります。

_func allEqual(a: Int, b: Int, c: Int) { return a == b && b == c }
func allEqual(a: String, b: String, c: String) { return a == b && b == c }
func allEqual(a: Samophlange, b: Samophlange, c: Samophlange) { return a == b && b == c }
_

ご覧のとおり、ここではコードを作成して新しい動作を実装しています—何かの要件のみを記述しているプロトコル関連の型とはかなり異なります他に満たすために。

TLDR

関連付けられた型とジェネリック型パラメーターは非常に異なる種類のツールです。関連付けられた型は説明の言語であり、ジェネリックは実装の言語です。それらは非常に異なる目的を持っていますが、用途が似ている場合があります(特に、任意の要素タイプのコレクションの抽象的な設計図と、任意の要素タイプのコレクションの実際のコレクションタイプとの間の、一目でわかるような微妙な違いについては特に)ジェネリック要素)。それらは非常に異なる獣であるので、それらは異なる構文を持っています。

参考文献

Swiftチームには、ジェネリック、プロトコル、および関連機能についての素敵な記事があります こちら

26
rickster