Tidak dapat secara eksplisit mengkhususkan fungsi generik

92

Saya memiliki masalah dengan kode berikut:

func generic1<T>(name : String){
}

func generic2<T>(name : String){
     generic1<T>(name)
}

hasil generic1 (nama) ke kesalahan penyusun "Tidak dapat secara eksplisit mengkhususkan fungsi generik"

Adakah cara untuk menghindari kesalahan ini? Saya tidak bisa mengubah tanda tangan dari fungsi generic1, oleh karena itu harus (String) -> Void

Greyisf
sumber
2
Apa gunanya menggunakan tipe generik di sini jika tidak bisa disimpulkan untuk konteksnya? Jika tipe generik hanya digunakan secara internal, Anda harus menentukan tipe di dalam tubuh fungsi.
Kirsteins
Apakah tipe placeholder Tdigunakan sama sekali generic1()? Bagaimana Anda memanggil fungsi itu, sehingga kompilator dapat menyimpulkan jenisnya?
Martin R
3
Saya berharap ada cara untuk memanggil fungsi seperti genetic1 <blaClass> ("SomeString")
Greyisf
2
Generik tidak hanya untuk kasus ketika kompilator dapat menyimpulkan konteksnya. Mengkhususkan fungsi secara jelas akan memungkinkan bagian lain dari kode untuk disimpulkan. ex: func foo<T>() -> T { ... }yang melakukan sesuatu dengan objek tbertipe Tdan return t. Mengkhususkan secara eksplisit Takan memungkinkan t1untuk diganggu var t1 = foo<T>(). Saya berharap ada cara untuk memanggil fungsi dengan cara ini juga. C # memungkinkannya
nacho4d
1
Aku juga mengalami ini juga. Tidak masuk akal untuk membuat fungsi generik jika Anda dipaksa untuk meneruskan tipe sebagai parameter. Ini pasti bug kan ?!
Nick

Jawaban:

161

Saya juga mengalami masalah ini dan saya menemukan solusi untuk kasus saya.

Pada artikel ini penulis memiliki masalah yang sama

https://www.iphonelife.com/blog/31369/swift-programming-101-generics-practical-guide

Jadi masalahnya tampaknya, kompilator perlu menyimpulkan tipe T entah bagaimana caranya. Tetapi tidak diperbolehkan untuk hanya menggunakan <type> (params ...) generik.

Biasanya, kompilator dapat mencari tipe T, dengan memindai tipe parameter karena di sinilah T digunakan dalam banyak kasus.

Dalam kasus saya itu sedikit berbeda, karena tipe kembalian dari fungsi saya adalah T. Dalam kasus Anda, tampaknya Anda belum menggunakan T sama sekali dalam fungsi Anda. Saya kira Anda baru saja menyederhanakan kode contoh.

Jadi saya memiliki fungsi berikut

func getProperty<T>( propertyID : String ) -> T

Dan dalam kasus, misalnya

getProperty<Int>("countProperty")

kompiler memberi saya kesalahan:

Tidak dapat secara eksplisit mengkhususkan fungsi generik

Jadi, untuk memberi kompiler sumber informasi lain untuk menyimpulkan jenis T, Anda harus secara eksplisit mendeklarasikan jenis variabel tempat nilai kembalian disimpan.

var value : Int = getProperty("countProperty")

Dengan cara ini kompilator mengetahui bahwa T harus berupa integer.

Jadi saya pikir secara keseluruhan itu hanya berarti bahwa jika Anda menentukan fungsi generik Anda harus setidaknya menggunakan T dalam tipe parameter Anda atau sebagai tipe kembali.

ThottChief
sumber
2
Menyelamatkan saya banyak waktu. Terima kasih.
chrislarson
4
Apakah ada cara untuk melakukannya jika tidak ada nilai pengembalian? yaitu,func updateProperty<T>( propertyID : String )
Kyle Bashour
1
Saya tidak mengerti mengapa Anda ingin melakukan itu? Karena Anda tidak mengembalikan apa pun, tidak ada manfaatnya mendeklarasikan fungsi generik Anda.
ThottChief
@ThottChief ada banyak manfaat misalnya jika Anda menggunakan / membangun keypaths, atau mendapatkan nama tipe sebagai string dll.
zaitsman
Jawaban bagus terima kasih. Saya berharap ini akan berhasil let value = foo() as? Typesehingga dapat digunakan di ifatau guardjika hasilnya opsional, tetapi tidak ...
agirault
54

Cepat 5

Biasanya ada banyak cara untuk mendefinisikan fungsi generik. Tetapi mereka didasarkan pada kondisi yang Tharus digunakan sebagai parameter, atau sebagai a return type.

extension UIViewController {
    class func doSomething<T: UIView>() -> T {
        return T()
    }

    class func doSomethingElse<T: UIView>(value: T) {
        // Note: value is a instance of T
    }

    class func doLastThing<T: UIView>(value: T.Type) {
        // Note: value is a MetaType of T
    }
}

Setelah itu, kita harus menyediakan Tsaat menelepon.

let result = UIViewController.doSomething() as UIImageView // Define `T` by casting, as UIImageView
let result: UILabel = UIViewController.doSomething() // Define `T` with property type, as UILabel
UIViewController.doSomethingElse(value: UIButton()) // Define `T` with parameter type, as UIButton
UIViewController.doLastThing(value: UITextView.self) // Define `T` with parameter type, as UITextView

Ref:

  1. http://austinzheng.com/2015/01/02/swift-generics-pt-1/
  2. https://dispatchswift.com/type-constraints-for-generics-in-swift-d6bf2f0dbbb2
nahung89
sumber
Jawaban bagus untuk masalah umum ... karena ada banyak cara untuk memperbaikinya. Saya juga menyadari self.result = UIViewController.doSomething()bekerja sendiri selama Anda mengetik properti saat Anda mendeklarasikannya.
teradyl
jawaban yang bagus menjelaskan banyak hal.
Okhan Okbay
Java doLastThing<UITextView>()menjadi Swift doLastThing(UITextView.self), yang ... bukan yang terburuk, setidaknya. Lebih baik daripada harus mengetikkan hasil yang rumit secara eksplisit. Terima kasih atas solusinya.
Erhannis
18

Solusinya adalah mengambil tipe kelas sebagai parameter (seperti di Java)

Untuk memberi tahu compiler tipe apa yang dia hadapi, berikan kelas sebagai argumen

extension UIViewController {
    func navigate<ControllerType: UIViewController>(_ dump: ControllerType.Type, id: String, before: ((ControllerType) -> Void)?){
        let controller = self.storyboard?.instantiateViewController(withIdentifier: id) as! ControllerType
        before?(controller)
        self.navigationController?.pushViewController(controller, animated: true)
    }
}

Panggil sebagai:

self.navigate(UserDetailsViewController.self, id: "UserDetailsViewController", before: {
        controller in
        controller.user = self.notification.sender
    })
Orkhan Alikhanov
sumber
1
Ini bagus dan jauh lebih baik daripada jawaban yang diterima. Harap pertimbangkan untuk mengedit untuk menghapus bagian historis, atau setidaknya letakkan di bawah solusi. Terima kasih!
Dan Rosenstark
1
@DanRosenark Terima kasih atas umpan baliknya :)
Orkhan Alikhanov
Meskipun ini berhasil, saya pikir itu bukan praktik terbaik. Alasannya adalah sekarang definisi fungsi membuat keputusan tentang apa yang harus dilakukan jika ada masalah dengan pemerannya. Jadi, di sini Anda hanya mengalami crash jika gips gagal. Lebih baik membiarkan klien memutuskan apa yang harus dilakukan karena ini mungkin berbeda untuk setiap klien. Jadi, saya akan memilih jawaban ini untuk alasan ini.
smileBot
@smileBot Maksud Anda contohnya buruk atau pendekatannya?
Orkhan Alikhanov
Pendekatan. Saya pikir itu adalah aturan umum pemrograman untuk menunda spesialisasi saat praktis. Ini adalah bagian dari gagasan memahami tanggung jawab. Ini bukan tanggung jawab fungsi untuk mengkhususkan pada generik. Ini adalah tanggung jawab pemanggil, karena fungsinya tidak dapat mengetahui apa yang dibutuhkan oleh semua pemanggil. Jika fungsi tersebut masuk ke peran ini maka ini membatasi penggunaan fungsi tersebut tanpa keuntungan. Anda dapat menggeneralisasi ini untuk banyak masalah pengkodean. Konsep tunggal ini telah sangat membantu saya.
smileBot
4

Anda tidak memerlukan generik di sini karena Anda memiliki tipe statis (String sebagai parameter), tetapi jika Anda ingin memiliki pemanggilan fungsi generik yang lain, Anda dapat melakukan hal berikut.

Menggunakan metode Generik

func fetchObjectOrCreate<T: NSManagedObject>(type: T.Type) -> T {
    if let existing = fetchExisting(type) {
       return existing
    }
    else {
        return createNew(type)
    }
}

func fetchExisting<T: NSManagedObject>(type: T.Type) -> T {
    let entityName = NSStringFromClass(type)
     // Run query for entiry
} 

func createNew<T: NSManagedObject>(type: T.Type) -> T {
     let entityName = NSStringFromClass(type)
     // create entity with name
} 

Menggunakan kelas generik (Kurang fleksibel karena generik hanya dapat didefinisikan untuk 1 jenis saja per instance)

class Foo<T> {

   func doStuff(text: String) -> T {
      return doOtherStuff(text)
   }

   func doOtherStuff(text: String) -> T {

   }  

}

let foo = Foo<Int>()
foo.doStuff("text")
aryaxt
sumber
3

Saya pikir ketika Anda menentukan fungsi generik Anda harus menentukan beberapa parameter tipe T, seperti berikut:

func generic1<T>(parameter: T) {
    println("OK")
}

func generic2<T>(parameter: T) {
    generic1(parameter)
}

dan jika Anda ingin memanggil metode handle (), maka Anda dapat melakukannya dengan menulis protokol, dan menentukan batasan tipe untuk T:

protocol Example {
    func handle() -> String
}

extension String: Example {
    func handle() -> String {
        return "OK"
    }
}

func generic1<T: Example>(parameter: T) {
    println(parameter.handle())
}

func generic2<T: Example>(parameter: T) {
    generic1(parameter)
}

jadi Anda dapat memanggil fungsi umum ini dengan String:

generic2("Some")

dan itu akan dikompilasi

Valerii Lider
sumber
1

Sejauh ini, praktik terbaik pribadi saya menggunakan jawaban @ orkhan-alikhanov. Hari ini, ketika melihat SwiftUI dan bagaimana .modifier()serta ViewModifierimplementasinya, saya menemukan cara lain (atau lebih merupakan solusi?)

Cukup bungkus fungsi kedua menjadi a struct.

Contoh:

Jika yang ini memberi Anda "Tidak dapat secara eksplisit mengkhususkan fungsi generik"

func generic2<T>(name: String){
     generic1<T>(name)
}

Yang ini mungkin bisa membantu. Bungkus deklarasi generic1menjadi struct:

struct Generic1Struct<T> {
    func generic1(name: String) {## do, whatever it needs with T ##}
}

dan menyebutnya dengan:

func generic2<T>(name : String){
     Generic1Struct<T>().generic1(name: name)
}

Catatan:

  • Saya tidak tahu apakah ini membantu dalam kasus apa pun, ketika pesan kesalahan ini terjadi. Saya hanya tahu, bahwa saya terjebak berkali-kali ketika hal ini muncul. Saya tahu, bahwa solusi ini membantu hari ini, ketika pesan kesalahan muncul.
  • Cara Swift menangani Generik bagi saya masih membingungkan.
  • Contoh ini dan solusi dengan structini adalah contoh yang bagus. Solusi di sini tidak memiliki lebih banyak informasi - tetapi meneruskan kompiler. Informasi yang sama, tetapi hasilnya berbeda? Lalu ada yang salah. Jika ini adalah bug kompilator, ini bisa diperbaiki.
jboi
sumber
0

Saya memiliki masalah serupa dengan fungsi kelas generik saya class func retrieveByKey<T: GrandLite>(key: String) -> T?.

Saya tidak bisa menyebutnya di let a = retrieveByKey<Categories>(key: "abc")mana Kategori adalah subclass dari GrandLite.

let a = Categories.retrieveByKey(key:"abc")mengembalikan GrandLite, bukan Kategori. Fungsi generik tidak menyimpulkan tipe berdasarkan kelas yang memanggilnya.

class func retrieveByKey<T: GrandLite>(aType: T, key: String>) -> T?memberi saya kesalahan ketika saya mencoba let a = Categories.retrieveByKey(aType: Categories, key: "abc")memberi saya kesalahan yang tidak dapat mengonversi Kategori. Ketik ke GrandLite, meskipun Kategori adalah subkelas dari GrandLite. NAMUN...

class func retrieveByKey<T: GrandLite>(aType: [T], key: String) -> T? melakukan pekerjaan jika saya mencoba let a = Categories.retrieveByKey(aType: [Categories](), key: "abc")ternyata tugas eksplisit dari subclass tidak bekerja, tetapi assigment implisit menggunakan jenis generik lain (array) tidak bekerja di Swift 3.

adamek
sumber
1
Untuk memperbaiki kesalahan, Anda harus memberikan aTypecontoh T, bukan menempatkan Categoriesdirinya sendiri. Ex: let a = Categories.retrieveByKey(aType: Categories(), key: "abc"). Solusi lain ditentukan aType: T.Type. Kemudian panggil metode tersebut sebagailet a = Categories.retrieveByKey(aType: Categories.self, key: "abc")
nahung89