Protokol tidak sesuai dengan dirinya sendiri?

125

Mengapa kode Swift ini tidak dikompilasi?

protocol P { }
struct S: P { }

let arr:[P] = [ S() ]

extension Array where Element : P {
    func test<T>() -> [T] {
        return []
    }
}

let result : [S] = arr.test()

Kompiler mengatakan: "Tipe Ptidak sesuai dengan protokol P" (atau, dalam versi Swift nanti, "Menggunakan 'P' sebagai tipe konkret yang sesuai dengan protokol 'P' tidak didukung.").

Kenapa tidak? Ini terasa seperti lubang dalam bahasa, entah bagaimana. Saya menyadari bahwa masalahnya berasal dari menyatakan array arrsebagai array dari tipe protokol , tetapi apakah itu hal yang tidak masuk akal untuk dilakukan? Saya pikir protokol ada di sana untuk membantu menyediakan struct dengan sesuatu seperti hierarki tipe?

matt
sumber
1
Saat Anda menghapus anotasi jenis di let arrbaris, kompiler menyimpulkan tipe [S]dan kompilasi kode. Sepertinya tipe protokol tidak dapat digunakan dengan cara yang sama seperti hubungan kelas - kelas super.
vadian
1
@ Vadian Benar, itulah yang saya maksudkan dalam pertanyaan saya ketika saya berkata "Saya menyadari bahwa masalahnya berasal dari menyatakan array array sebagai array dari tipe protokol". Tetapi, seperti yang saya katakan selanjutnya dalam pertanyaan saya, inti dari protokol biasanya adalah bahwa protokol tersebut dapat digunakan dengan cara yang sama seperti hubungan kelas-superclass! Mereka dimaksudkan untuk memberikan semacam struktur hierarkis kepada dunia struct. Dan mereka biasanya melakukannya. Pertanyaannya adalah, mengapa itu tidak berhasil di sini ?
matt
1
Masih tidak berfungsi di Xcode 7.1, tetapi pesan kesalahan sekarang "menggunakan 'P' sebagai tipe konkret yang sesuai dengan protokol 'P' tidak didukung" .
Martin R
1
@ MartinR Ini pesan kesalahan yang lebih baik. Namun bagi saya rasanya seperti lubang dalam bahasa itu.
matt
Tentu! Bahkan dengan protocol P : Q { }, P tidak sesuai dengan Q.
Martin R

Jawaban:

66

EDIT: Delapan belas bulan lagi bekerja dengan Swift, rilis besar lainnya (yang menyediakan diagnostik baru), dan komentar dari @ AyBayBay membuat saya ingin menulis ulang jawaban ini. Diagnostik baru adalah:

"Menggunakan 'P' sebagai tipe konkret yang sesuai dengan protokol 'P' tidak didukung."

Itu sebenarnya membuat semua ini jauh lebih jelas. Ekstensi ini:

extension Array where Element : P {

tidak berlaku ketika Element == Pkarena Ptidak dianggap sebagai konkret konkret dari P. (Solusi "letakkan di dalam kotak" di bawah ini masih merupakan solusi yang paling umum.)


Jawaban lama:

Ini adalah kasus lain dari metatypes. Swift benar-benar ingin Anda mendapatkan tipe konkret untuk sebagian besar hal-hal yang tidak sepele. [P]bukan tipe konkret (Anda tidak dapat mengalokasikan blok memori dengan ukuran yang diketahui untuk P). (Saya tidak berpikir itu benar; Anda benar-benar dapat membuat sesuatu dengan ukuran Pkarena dilakukan melalui tipuan .) Saya tidak berpikir ada bukti bahwa ini adalah kasus pekerjaan yang "tidak boleh". Ini terlihat seperti salah satu dari kasus "belum berfungsi". (Sayangnya hampir tidak mungkin untuk mendapatkan Apple untuk mengkonfirmasi perbedaan antara kasus-kasus itu.) Fakta bahwa Array<P>bisa menjadi tipe variabel (di manaArraytidak dapat) menunjukkan bahwa mereka telah melakukan beberapa pekerjaan ke arah ini, tetapi metatyp Swift memiliki banyak tepi tajam dan kasus yang tidak diimplementasikan. Saya tidak berpikir Anda akan mendapatkan jawaban "mengapa" yang lebih baik dari itu. "Karena kompiler tidak mengizinkannya." (Tidak memuaskan, saya tahu. Seluruh hidup Swift saya ...)

Solusinya hampir selalu menempatkan barang-barang di dalam kotak. Kami membangun penghapus tipe.

protocol P { }
struct S: P { }

struct AnyPArray {
    var array: [P]
    init(_ array:[P]) { self.array = array }
}

extension AnyPArray {
    func test<T>() -> [T] {
        return []
    }
}

let arr = AnyPArray([S()])
let result: [S] = arr.test()

Ketika Swift memungkinkan Anda untuk melakukan ini secara langsung (yang saya harapkan nantinya), kemungkinan besar hanya dengan membuat kotak ini untuk Anda secara otomatis. Enum rekursif memiliki persis sejarah ini. Anda harus mengepaknya dan itu sangat menjengkelkan dan membatasi, dan akhirnya kompiler menambahkan indirectuntuk melakukan hal yang sama secara lebih otomatis.

Rob Napier
sumber
Banyak informasi berguna dalam jawaban ini, tetapi solusi aktual dalam jawaban Tomohiro lebih baik daripada solusi tinju yang disajikan di sini.
jsadler
@ jsadler Pertanyaannya bukan bagaimana mengatasi batasan, tetapi mengapa batasan itu ada. Memang, sejauh penjelasan berjalan, solusi Tomohiro menimbulkan lebih banyak pertanyaan daripada jawaban. Jika kita gunakan ==dalam contoh Array saya, kami mendapatkan kesalahan, persyaratan jenis yang sama membuat parameter generik 'Elemen' non-generik. "Mengapa penggunaan Tomohiro tentang ==menghasilkan kesalahan yang sama?
matt
@Rob Napier Saya masih bingung dengan jawaban Anda. Bagaimana Swift melihat lebih banyak konkrit dalam solusi Anda vs yang asli? Anda sepertinya baru saja membungkus sesuatu dalam sebuah struct ... Idk mungkin saya sedang berjuang untuk memahami sistem tipe cepat tetapi ini semua tampak seperti magic voodoo
AyBayBay
@AyBayBay Jawaban yang diperbarui.
Rob Napier
Terima kasih banyak @RobNapier Saya selalu kagum dengan kecepatan balasan Anda dan terus terang bagaimana Anda menemukan waktu untuk membantu orang sebanyak yang Anda lakukan. Namun demikian, suntingan Anda yang baru pasti menempatkannya dalam perspektif. Satu hal lagi yang ingin saya tunjukkan, memahami penghapusan tipe juga membantu saya. Artikel ini khususnya melakukan pekerjaan yang fantastis: krakendev.io/blog/generic-protocols-and-their-shortcomings TBH Idk bagaimana perasaan saya tentang beberapa hal ini. Sepertinya kita memperhitungkan lubang-lubang dalam bahasa tersebut tetapi Idk bagaimana apel akan membuat sebagian dari ini.
AyBayBay
109

Mengapa protokol tidak sesuai dengan diri mereka sendiri?

Mengizinkan protokol untuk menyesuaikan diri dengan diri mereka sendiri dalam kasus umum tidak sehat. Masalahnya terletak pada persyaratan protokol statis.

Ini termasuk:

  • static metode dan properti
  • Inisialisasi
  • Jenis terkait (meskipun saat ini mencegah penggunaan protokol sebagai jenis aktual)

Kami dapat mengakses persyaratan ini pada placeholder generik di Tmana T : P- namun kami tidak dapat mengaksesnya pada tipe protokol itu sendiri, karena tidak ada tipe konkret yang konkret untuk diteruskan. Oleh karena itu kita tidak bisa membiarkan Tmenjadi P.

Pertimbangkan apa yang akan terjadi dalam contoh berikut jika kami mengizinkan Arrayekstensi berlaku untuk [P]:

protocol P {
  init()
}

struct S  : P {}
struct S1 : P {}

extension Array where Element : P {
  mutating func appendNew() {
    // If Element is P, we cannot possibly construct a new instance of it, as you cannot
    // construct an instance of a protocol.
    append(Element())
  }
}

var arr: [P] = [S(), S1()]

// error: Using 'P' as a concrete type conforming to protocol 'P' is not supported
arr.appendNew()

Kita tidak mungkin memanggil appendNew()a [P], karena P(the Element) bukan tipe konkret dan karenanya tidak dapat dipakai. Itu harus dipanggil pada array dengan elemen bertipe beton, di mana tipe itu sesuai dengan P.

Ceritanya mirip dengan metode statis dan persyaratan properti:

protocol P {
  static func foo()
  static var bar: Int { get }
}

struct SomeGeneric<T : P> {

  func baz() {
    // If T is P, what's the value of bar? There isn't one – because there's no
    // implementation of bar's getter defined on P itself.
    print(T.bar)

    T.foo() // If T is P, what method are we calling here?
  }
}

// error: Using 'P' as a concrete type conforming to protocol 'P' is not supported
SomeGeneric<P>().baz()

Kami tidak dapat berbicara dalam hal SomeGeneric<P>. Kita membutuhkan implementasi konkret dari persyaratan protokol statis (perhatikan bagaimana tidak ada implementasi foo()atau bardidefinisikan dalam contoh di atas). Meskipun kami dapat mendefinisikan implementasi persyaratan ini dalam Pekstensi, ini hanya ditentukan untuk jenis konkret yang sesuai P- Anda masih tidak dapat memanggilnya Psendiri.

Karena itu, Swift tidak mengizinkan kami menggunakan protokol sebagai tipe yang sesuai dengan dirinya sendiri - karena ketika protokol itu memiliki persyaratan statis, Swift tidak melakukannya.

Persyaratan protokol instan tidak bermasalah, karena Anda harus memanggilnya pada instance aktual yang sesuai dengan protokol (dan karenanya harus menerapkan persyaratan tersebut). Jadi, ketika memanggil suatu persyaratan pada instance yang diketikkan P, kita bisa meneruskan permintaan itu ke implementasi tipe konkret yang mendasari persyaratan itu.

Namun membuat pengecualian khusus untuk aturan dalam kasus ini dapat menyebabkan inkonsistensi yang mengejutkan dalam bagaimana protokol diperlakukan oleh kode generik. Meskipun demikian, situasinya tidak terlalu berbeda dengan associatedtypepersyaratan - yang (saat ini) mencegah Anda menggunakan protokol sebagai tipe. Memiliki batasan yang mencegah Anda menggunakan protokol sebagai tipe yang sesuai dengan dirinya sendiri ketika memiliki persyaratan statis bisa menjadi opsi untuk versi bahasa yang akan datang

Sunting: Dan seperti yang dieksplorasi di bawah, ini terlihat seperti apa yang dituju oleh tim Swift.


@objc protokol

Dan pada kenyataannya, sebenarnya itulah cara bahasa memperlakukan @objcprotokol. Ketika mereka tidak memiliki persyaratan statis, mereka menyesuaikan diri.

Kompilasi berikut baik-baik saja:

import Foundation

@objc protocol P {
  func foo()
}

class C : P {
  func foo() {
    print("C's foo called!")
  }
}

func baz<T : P>(_ t: T) {
  t.foo()
}

let c: P = C()
baz(c)

bazmengharuskan yang Tsesuai dengan P; tapi kita dapat menggantikan di Puntuk Tkarena Ptidak memiliki persyaratan statis. Jika kita menambahkan persyaratan statis P, contoh tidak lagi mengkompilasi:

import Foundation

@objc protocol P {
  static func bar()
  func foo()
}

class C : P {

  static func bar() {
    print("C's bar called")
  }

  func foo() {
    print("C's foo called!")
  }
}

func baz<T : P>(_ t: T) {
  t.foo()
}

let c: P = C()
baz(c) // error: Cannot invoke 'baz' with an argument list of type '(P)'

Jadi satu solusi untuk masalah ini adalah membuat protokol Anda @objc. Memang, ini bukan solusi yang ideal dalam banyak kasus, karena memaksa tipe yang sesuai Anda menjadi kelas, serta membutuhkan runtime Obj-C, karena itu tidak membuatnya layak pada platform non-Apple seperti Linux.

Tetapi saya menduga bahwa batasan ini adalah (salah satu) alasan utama mengapa bahasa sudah mengimplementasikan 'protokol tanpa persyaratan statis sesuai dengan dirinya sendiri' untuk @objcprotokol. Kode generik yang ditulis di sekitarnya dapat disederhanakan secara signifikan oleh kompiler.

Mengapa? Karena @objcnilai yang diketikkan protokol secara efektif hanya referensi kelas yang persyaratannya dikirim menggunakan objc_msgSend. Di sisi lain, @objcnilai-nilai yang tidak diketikkan protokol lebih rumit, karena membawa sekitar nilai dan tabel saksi untuk mengelola memori nilai yang dibungkus (berpotensi disimpan secara tidak langsung) dan untuk menentukan implementasi apa yang diperlukan untuk perbedaan. persyaratan, masing-masing.

Karena representasi @objcprotokol yang disederhanakan ini , nilai tipe protokol seperti itu Pdapat berbagi representasi memori yang sama dengan 'nilai generik' dari tipe placeholder generik T : P, mungkin memudahkan tim Swift untuk memungkinkan penyesuaian diri. Hal yang sama tidak berlaku untuk non- @objcprotokol namun karena nilai generik seperti saat ini tidak membawa nilai atau tabel saksi protokol.

Namun fitur ini disengaja dan mudah-mudahan akan diluncurkan ke non- @objcprotokol, seperti yang dikonfirmasi oleh anggota tim Swift Slava Pestov di komentar SR-55 dalam menanggapi pertanyaan Anda tentang hal itu (diminta oleh pertanyaan ini ):

Matt Neuburg menambahkan komentar - 7 Sep 2017 1:33 PM

Ini mengkompilasi:

@objc protocol P {}
class C: P {}

func process<T: P>(item: T) -> T { return item }
func f(image: P) { let processed: P = process(item:image) }

Menambahkan @objcmembuatnya mengkompilasi; menghapusnya membuatnya tidak dikompilasi lagi. Beberapa dari kita di Stack Overflow menemukan ini mengejutkan dan ingin tahu apakah itu disengaja atau buggy edge-case.

Slava Pestov menambahkan komentar - 7 Sep 2017 13:53

Ini disengaja - mengangkat batasan ini tentang apa bug ini. Seperti yang saya katakan itu rumit dan kami belum punya rencana konkret.

Jadi mudah-mudahan itu adalah sesuatu yang suatu hari nanti akan mendukung untuk non- @objcprotokol juga.

Tetapi solusi apa yang ada saat ini untuk non- @objcprotokol?


Menerapkan ekstensi dengan batasan protokol

Di Swift 3.1, jika Anda menginginkan ekstensi dengan batasan yang pengganti generik atau tipe terkait yang diberikan haruslah tipe protokol tertentu (bukan hanya tipe konkret yang sesuai dengan protokol itu) - Anda bisa mendefinisikan ini dengan ==kendala.

Misalnya, kami dapat menulis ekstensi array Anda sebagai:

extension Array where Element == P {
  func test<T>() -> [T] {
    return []
  }
}

let arr: [P] = [S()]
let result: [S] = arr.test()

Tentu saja, ini sekarang mencegah kita dari menyebutnya pada array dengan elemen tipe konkret yang sesuai P. Kita dapat menyelesaikan ini dengan hanya mendefinisikan ekstensi tambahan untuk kapan Element : P, dan hanya meneruskan ke == Pekstensi:

extension Array where Element : P {
  func test<T>() -> [T] {
    return (self as [P]).test()
  }
}

let arr = [S()]
let result: [S] = arr.test()

Namun perlu dicatat bahwa ini akan melakukan konversi O (n) dari array ke [P], karena setiap elemen harus dikotakkan dalam wadah eksistensial. Jika kinerja merupakan masalah, Anda bisa menyelesaikannya dengan menerapkan kembali metode ekstensi. Ini bukan solusi yang sepenuhnya memuaskan - semoga versi bahasa yang akan datang akan mencakup cara untuk mengekspresikan batasan 'tipe protokol atau sesuai dengan tipe protokol'.

Sebelum ke Swift 3.1, cara paling umum untuk mencapai ini, seperti yang ditunjukkan Rob dalam jawabannya , adalah dengan hanya membangun tipe pembungkus untuk a [P], yang kemudian Anda dapat mendefinisikan metode ekstensi Anda.


Melewati instance yang diketikkan protokol ke placeholder generik terbatas

Pertimbangkan situasi berikut (dibuat-buat, tetapi tidak jarang):

protocol P {
  var bar: Int { get set }
  func foo(str: String)
}

struct S : P {
  var bar: Int
  func foo(str: String) {/* ... */}
}

func takesConcreteP<T : P>(_ t: T) {/* ... */}

let p: P = S(bar: 5)

// error: Cannot invoke 'takesConcreteP' with an argument list of type '(P)'
takesConcreteP(p)

Kami tidak bisa lewat pke takesConcreteP(_:), karena kita tidak bisa saat menggantikan Puntuk placeholder generik T : P. Mari kita lihat beberapa cara untuk menyelesaikan masalah ini.

1. Membuka eksistensial

Daripada mencoba untuk mengganti Puntuk T : P, bagaimana jika kita bisa menggali ke dalam jenis beton yang mendasari bahwa Pnilai diketik adalah pembungkus dan pemain pengganti bahwa alih-alih? Sayangnya, ini memerlukan fitur bahasa yang disebut dengan membuka eksistensial , yang saat ini tidak tersedia secara langsung untuk pengguna.

Namun, Swift tidak secara implisit existentials terbuka (protocol-diketik nilai) ketika mengakses anggota pada mereka (yakni menggali keluar jenis runtime dan membuatnya dapat diakses dalam bentuk sebuah tempat generik). Kami dapat memanfaatkan fakta ini dalam ekstensi protokol di P:

extension P {
  func callTakesConcreteP/*<Self : P>*/(/*self: Self*/) {
    takesConcreteP(self)
  }
}

Perhatikan Selfplaceholder generik implisit yang diambil oleh metode ekstensi, yang digunakan untuk mengetik selfparameter implisit - ini terjadi di belakang layar dengan semua anggota ekstensi protokol. Saat memanggil metode seperti itu pada nilai ketik protokol P, Swift menggali tipe beton yang mendasarinya, dan menggunakannya untuk memuaskan Selfplaceholder generik. Inilah sebabnya mengapa kami dapat memanggil takesConcreteP(_:)dengan self- kami memuaskan Tdengan Self.

Ini berarti bahwa sekarang kita dapat mengatakan:

p.callTakesConcreteP()

Dan takesConcreteP(_:)dipanggil dengan placeholder generiknya Tyang puas dengan jenis beton yang mendasarinya (dalam hal ini S). Perhatikan bahwa ini bukan "protokol yang sesuai dengan diri mereka sendiri", karena kami mengganti tipe konkret daripada P- coba tambahkan persyaratan statis pada protokol dan lihat apa yang terjadi ketika Anda menyebutnya dari dalam takesConcreteP(_:).

Jika Swift terus melarang protokol agar sesuai dengan dirinya sendiri, alternatif terbaik berikutnya adalah secara implisit membuka eksistensial ketika mencoba meneruskannya sebagai argumen ke parameter tipe generik - secara efektif melakukan persis apa yang dilakukan trampolin ekstensi protokol kami, hanya tanpa boilerplate.

Namun perhatikan bahwa membuka eksistensial bukanlah solusi umum untuk masalah protokol yang tidak sesuai dengan diri mereka sendiri. Itu tidak berurusan dengan koleksi heterogen dari nilai-nilai yang diketikkan protokol, yang semuanya mungkin memiliki tipe beton mendasar yang berbeda. Sebagai contoh, pertimbangkan:

struct Q : P {
  var bar: Int
  func foo(str: String) {}
}

// The placeholder `T` must be satisfied by a single type
func takesConcreteArrayOfP<T : P>(_ t: [T]) {}

// ...but an array of `P` could have elements of different underlying concrete types.
let array: [P] = [S(bar: 1), Q(bar: 2)]

// So there's no sensible concrete type we can substitute for `T`.
takesConcreteArrayOfP(array) 

Untuk alasan yang sama, fungsi dengan beberapa Tparameter juga akan bermasalah, karena parameter harus mengambil argumen dengan tipe yang sama - namun jika kita memiliki dua Pnilai, tidak ada cara kita dapat menjamin pada waktu kompilasi bahwa keduanya memiliki beton dasar yang sama Tipe.

Untuk mengatasi masalah ini, kita bisa menggunakan penghapus tipe.

2. Bangun penghapus tipe

Seperti kata Rob , penghapus tipe , adalah solusi paling umum untuk masalah protokol yang tidak sesuai dengan diri mereka sendiri. Mereka memungkinkan kita untuk membungkus instance yang diketikkan protokol dalam tipe konkret yang sesuai dengan protokol itu, dengan meneruskan persyaratan instance ke instance yang mendasarinya.

Jadi, mari kita membangun kotak penghapusan tipe yang meneruskan Ppersyaratan instance ke instance arbitrase yang mendasari yang sesuai dengan P:

struct AnyP : P {

  private var base: P

  init(_ base: P) {
    self.base = base
  }

  var bar: Int {
    get { return base.bar }
    set { base.bar = newValue }
  }

  func foo(str: String) { base.foo(str: str) }
}

Sekarang kita bisa berbicara dalam hal AnyPalih-alih P:

let p = AnyP(S(bar: 5))
takesConcreteP(p)

// example from #1...
let array = [AnyP(S(bar: 1)), AnyP(Q(bar: 2))]
takesConcreteArrayOfP(array)

Sekarang, pertimbangkan sejenak mengapa kami harus membangun kotak itu. Seperti yang kita bahas lebih awal, Swift membutuhkan jenis konkret untuk kasus-kasus di mana protokol memiliki persyaratan statis. Pertimbangkan jika Pmemiliki persyaratan statis - kita perlu mengimplementasikannya di AnyP. Tetapi apa yang seharusnya diimplementasikan sebagai? Kita berhadapan dengan contoh arbitrer yang sesuai dengan di Psini - kita tidak tahu tentang bagaimana tipe konkret yang mendasarinya menerapkan persyaratan statis, oleh karena itu kita tidak dapat mengungkapkannya secara bermakna AnyP.

Oleh karena itu, solusi dalam kasus ini hanya benar-benar berguna dalam hal persyaratan protokol instance . Dalam kasus umum, kita masih tidak dapat memperlakukan Psebagai tipe konkret yang sesuai P.

Hamish
sumber
2
Mungkin saya hanya menjadi padat, tetapi saya tidak mengerti mengapa kasus statis itu istimewa. Kita (kompiler) tahu sama banyak atau sedikit tentang properti statis prototol pada waktu kompilasi seperti yang kita tahu tentang properti instance protokol, yaitu bahwa pengadopsi akan mengimplementasikannya. Jadi apa bedanya?
matt
1
@matt Sebuah instance bertipe protokol (yaitu instance bertipe beton yang dibungkus dengan eksistensial P) baik-baik saja karena kita bisa meneruskan panggilan ke persyaratan instance ke instance yang mendasarinya. Namun, untuk tipe protokol itu sendiri (yaitu a P.Protocol, secara harfiah hanya tipe yang menggambarkan protokol) - tidak ada yang mengadopsi, oleh karena itu tidak ada yang perlu disebut persyaratan statis, itulah sebabnya dalam contoh di atas kita tidak dapat memiliki SomeGeneric<P>(Itu berbeda untuk P.Type(metatype eksistensial), yang menggambarkan metatipe konkret dari sesuatu yang sesuai dengan P- tapi itu cerita lain)
Hamish
Pertanyaan yang saya ajukan di bagian atas halaman ini adalah mengapa adopter tipe protokol baik-baik saja dan tipe protokol itu sendiri tidak. Saya mengerti bahwa untuk tipe protokol itu sendiri tidak ada yang mengadopsi. - Apa yang saya tidak mengerti adalah mengapa lebih sulit untuk meneruskan panggilan statis ke jenis adopsi daripada untuk meneruskan panggilan contoh ke jenis adopsi. Anda membuat argumen bahwa alasan ada kesulitan di sini adalah karena sifat persyaratan statis pada khususnya, tetapi saya tidak melihat bagaimana persyaratan statis lebih sulit daripada persyaratan contoh.
matt
@ Matt Bukan karena persyaratan statis "lebih keras" dari persyaratan instance - kompiler dapat menangani keduanya dengan baik melalui eksistensial untuk instance (yaitu instance diketik sebagai P) dan metatyp eksistensial (yaitu P.Typemetatypes). Masalahnya adalah bahwa untuk obat generik - kita tidak benar-benar membandingkan suka untuk suka. Ketika Tini P, tidak ada underyling beton (meta) jenis persyaratan maju statis untuk ( Tadalah P.Protocol, tidak P.Type) ....
Hamish
1
Saya benar-benar tidak peduli dengan kesehatan dll, saya hanya ingin menulis aplikasi, dan jika terasa seperti itu harus berfungsi, seharusnya begitu. Bahasa seharusnya hanya alat, bukan produk itu sendiri. Jika ada beberapa kasus yang benar-benar tidak berfungsi untuk itu maka boleh saja tidak mengizinkannya dalam kasus tersebut, tetapi biarkan semua orang menggunakan kasing yang berfungsi dan biarkan mereka melanjutkan dengan menulis aplikasi.
Jonathan.
17

Jika Anda memperluas CollectionTypeprotokol alih-alih Arraydan membatasi oleh protokol sebagai tipe konkret, Anda dapat menulis ulang kode sebelumnya sebagai berikut.

protocol P { }
struct S: P { }

let arr:[P] = [ S() ]

extension CollectionType where Generator.Element == P {
    func test<T>() -> [T] {
        return []
    }
}

let result : [S] = arr.test()
Tomohiro Kumagai
sumber
Saya tidak berpikir Koleksi vs Array yang relevan di sini, perubahan penting adalah menggunakan == Pvs : P. Dengan == contoh asli juga berfungsi. Dan masalah potensial (tergantung pada konteks) dengan == adalah tidak termasuk sub-protokol: jika saya membuat protocol SubP: P, dan kemudian mendefinisikan arrseperti [SubP]itu arr.test()tidak akan berfungsi lagi (kesalahan: SubP dan P harus setara).
imre