Pertanyaan Mengapa tipe-tipe yang terkait untuk protokol tidak menggunakan sintaksis tipe generik di Swift?


Saya bingung tentang perbedaan antara sintaks yang digunakan untuk jenis yang terkait untuk protokol, di satu sisi, dan tipe generik di sisi lain.

Di Swift, misalnya, seseorang dapat mendefinisikan tipe generik menggunakan sesuatu seperti

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

sementara yang satu mendefinisikan protokol dengan tipe yang terkait menggunakan sesuatu seperti

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

Mengapa yang terakhir saja:

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

Apakah ada alasan yang mendalam (atau mungkin hanya jelas dan hilang pada saya) bahwa bahasa tersebut belum mengadopsi sintaks yang terakhir?


32
2017-10-24 19:31


asal


Jawaban:


Ini telah dicakup beberapa kali pada devlist. Jawaban dasarnya adalah bahwa jenis yang terkait lebih fleksibel daripada parameter jenis. Meskipun Anda memiliki kasus khusus di sini dengan satu parameter jenis, sangat mungkin untuk memiliki beberapa parameter. Misalnya, Koleksi memiliki jenis Elemen, tetapi juga jenis Indeks dan jenis Generator. Jika Anda mengkhususkan mereka sepenuhnya dengan jenis parameterisasi, Anda harus membicarakan hal-hal seperti Array<String, Int, Generator<String>> atau sejenisnya.

Anda dapat melewati semua itu (Java tidak), tetapi kemudian Anda memiliki lebih sedikit cara untuk membatasi jenis Anda. Java sebenarnya sangat terbatas dalam hal bagaimana ia dapat membatasi jenis. Anda tidak dapat memiliki jenis pengindeksan acak pada koleksi Anda di Java. Scala memperluas sistem tipe Java dengan tipe terkait seperti Swift. Tipe-tipe yang terkait telah sangat kuat di Scala. Mereka juga merupakan sumber kebingungan dan kerontokan rambut.

Apakah kekuatan ekstra ini sepadan adalah pertanyaan yang sama sekali berbeda, dan hanya waktu yang akan memberi tahu. Tetapi jenis terkait pasti lebih kuat daripada tipe parameterisasi sederhana.


23
2017-10-24 19:45



Jawaban RobNapier (seperti biasa) cukup bagus, tetapi hanya untuk perspektif alternatif yang mungkin terbukti lebih mencerahkan ...

Pada Jenis Terkait

Protokol adalah seperangkat persyaratan abstrak - daftar periksa yang harus dipenuhi oleh jenis konkret untuk menyatakannya sesuai dengan protokol. Secara tradisional orang berpikir tentang daftar perilaku, yaitu: metode atau properti yang diterapkan oleh tipe beton. Tipe terkait adalah cara menamai hal-hal yang terlibat dalam daftar seperti itu, dan dengan demikian memperluas definisi sambil menjaganya tetap terbuka untuk bagaimana jenis yang sesuai mengimplementasikan kesesuaian.

Ketika kamu melihat:

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

Apa itu artinya adalah, untuk jenis klaim kesesuaian SimpleSetType, tidak hanya harus mengandung jenis itu insert(_:) dan contains(_:) fungsi, kedua fungsi tersebut harus mengambil jenis parameter yang sama satu sama lain. Tetapi tidak masalah apa jenis parameter itu.

Anda dapat menerapkan protokol ini dengan jenis generik atau non-generik:

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 { /*...*/ }
}    

Perhatikan bahwa tidak ada tempat BagOfBytes atau SetOfEquatables menentukan hubungan antara SimpleSetType.Element dan jenis yang digunakan sebagai parameter untuk dua metode mereka - kompilator secara otomatis bekerja bahwa jenis-jenis tersebut terkait dengan metode yang tepat, sehingga mereka memenuhi persyaratan protokol untuk tipe terkait.

Pada Parameter Tipe Umum

Di mana jenis terkait memperluas kosakata Anda untuk membuat daftar periksa abstrak, parameter tipe generik batasi implementasi dari tipe beton. Ketika Anda memiliki kelas generik seperti ini:

class ViewController<V: View> {
    var view: V
}

Itu tidak mengatakan bahwa ada banyak cara berbeda untuk membuat ViewController (selama Anda punya view), tertulis a ViewController  adalah nyata, hal konkret, dan itu memiliki view. Dan lebih jauh lagi, kita tidak tahu persis apa jenis pandangan yang diberikan ViewController Misalnya, tetapi kita tahu bahwa itu harus menjadi View (salah satu subkelas dari View kelas, atau tipe yang mengimplementasikan View protokol ... kami tidak bilang).

Atau dengan kata lain, menulis tipe generik atau fungsi adalah semacam cara pintas untuk menulis kode yang sebenarnya. Ambil contoh ini:

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

Ini memiliki efek yang sama seperti jika Anda melewati semua itu Equatable jenis dan menulis:

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 }

Seperti yang Anda lihat, kami membuat kode di sini, menerapkan perilaku baru - jauh berbeda dengan jenis protokol yang terkait di mana kami hanya menjelaskan persyaratan untuk sesuatu yang lain untuk dipenuhi.

TLDR

Jenis terkait dan tipe generik parameter sangat berbeda jenis alat: jenis terkait adalah bahasa deskripsi, dan generik adalah bahasa implementasi. Mereka memiliki tujuan yang sangat berbeda, meskipun penggunaannya terkadang terlihat mirip (terutama ketika menyangkut perbedaan-perbedaan yang sangat halus pada pandangan pertama seperti itu antara cetak biru abstrak untuk koleksi jenis elemen apa pun, dan jenis koleksi aktual yang masih dapat memiliki elemen generik). Karena mereka adalah binatang yang sangat berbeda, mereka memiliki sintaks yang berbeda.


20
2018-01-28 21:08