Non è possibile specializzare esplicitamente una funzione generica


91

Ho problemi con il seguente codice:

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

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

il risultato di generic1 (nome) per l'errore del compilatore "Impossibile specializzare esplicitamente una funzione generica"

C'è un modo per evitare questo errore? Non posso cambiare la firma della funzione generic1, quindi dovrebbe essere (String) -> Void


2
Qual è lo scopo di utilizzare il tipo generico qui quando non può essere dedotto per il contesto? Se il tipo generico viene utilizzato solo internamente, è necessario specificare il tipo all'interno del corpo della funzione.
Kirsteins

Il tipo di segnaposto è Tutilizzato affatto in generic1()? Come chiamereste quella funzione, in modo che il compilatore possa dedurre il tipo?
Martin R

3
Spero che ci sia un modo per chiamare una funzione come genetica1 <blaClass> ("SomeString")
Greyisf

1
I generici non sono solo per i casi in cui il compilatore può dedurre il contesto. La specializzazione esplicita della funzione consentirebbe di dedurre altre parti del codice. ex: func foo<T>() -> T { ... }che fa qualcosa con un oggetto tdi tipo Te il ritorno t. La specializzazione esplicita Tconsentirebbe t1di essere acquisiti var t1 = foo<T>(). Vorrei che ci fosse un modo per chiamare anche le funzioni in questo modo. C # lo consente
nacho4d

1
Mi sono imbattuto anche in questo. Non ha senso creare una funzione generica se sei costretto a passare il tipo come parametro. Deve essere un bug, vero ?!
Nick

Risposte:


160

Ho anche avuto questo problema e ho trovato una soluzione alternativa per il mio caso.

In questo articolo l'autore ha lo stesso problema

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

Quindi il problema sembra essere che il compilatore deve inferire il tipo di T in qualche modo. Ma non è consentito usare semplicemente il <tipo> generico (params ...).

Normalmente, il compilatore può cercare il tipo di T, scansionando i tipi di parametro perché è qui che in molti casi viene utilizzato T.

Nel mio caso è stato un po 'diverso, perché il tipo di ritorno della mia funzione era T. Nel tuo caso sembra che tu non abbia usato affatto T nella tua funzione. Immagino che tu abbia appena semplificato il codice di esempio.

Quindi ho la seguente funzione

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

E nel caso, per esempio

getProperty<Int>("countProperty")

il compilatore mi dà l'errore:

Non è possibile specializzare esplicitamente una funzione generica

Quindi, per fornire al compilatore un'altra fonte di informazioni da cui dedurre il tipo di T, è necessario dichiarare esplicitamente il tipo di variabile in cui viene salvato il valore restituito.

var value : Int = getProperty("countProperty")

In questo modo il compilatore sa che T deve essere un numero intero.

Quindi penso che nel complesso significhi semplicemente che se specifichi una funzione generica devi almeno usare T nei tuoi tipi di parametro o come tipo restituito.


2
Mi ha fatto risparmiare un sacco di tempo. Grazie.
chrislarson

3
C'è un modo per farlo se non c'è un valore di ritorno? cioè,func updateProperty<T>( propertyID : String )
Kyle Bashour

1
Non vedo perché vorresti farlo? Dal momento che non restituisci nulla, non c'è alcun vantaggio nel dichiarare la tua funzione generica.
ThottChief

@ThottChief ci sono molti vantaggi, ad esempio se stai usando / costruendo percorsi chiave, o ottieni il nome del tipo come stringa ecc.
zaitsman

Ottima risposta grazie. Vorrei che funzionasse in let value = foo() as? Typemodo che possa essere usato in a ifoppure guardil risultato è opzionale, ma non ...
agirault

53

Swift 5

In genere ci sono molti modi per definire funzioni generiche. Ma si basano su una condizione che Tdeve essere utilizzata parametercome un file 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
    }
}

Dopodiché, dobbiamo fornire Tquando si chiama.

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

Rif:

  1. http://austinzheng.com/2015/01/02/swift-generics-pt-1/
  2. https://dispatchswift.com/type-constraints-for-generics-in-swift-d6bf2f0dbbb2

Ottima risposta al problema generale ... poiché ci sono tanti modi per risolverlo. Ho anche realizzato che self.result = UIViewController.doSomething()funziona da solo fintanto che hai digitato la proprietà quando la dichiari.
teradyl

ottima risposta che spiega molte cose.
Okhan Okbay

Java doLastThing<UITextView>()diventa Swift doLastThing(UITextView.self), che è ... non il peggiore, almeno. Meglio che dover digitare esplicitamente risultati complicati. Grazie per la soluzione.
Erhannis

18

La soluzione sta prendendo il tipo di classe come parametro (come in Java)

Per far sapere al compilatore di che tipo ha a che fare, passare la classe come argomento

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)
    }
}

Chiama come:

self.navigate(UserDetailsViewController.self, id: "UserDetailsViewController", before: {
        controller in
        controller.user = self.notification.sender
    })

1
Questo è ottimo e molto meglio della risposta accettata. Si prega di prendere in considerazione la modifica per rimuovere la parte storica, o almeno metterla sotto la soluzione. Grazie!
Dan Rosenstark

1
@DanRosenstark Grazie per il feedback :)
Orkhan Alikhanov

Anche se funziona, non credo sia la migliore pratica. Il motivo è che ora la definizione della funzione prende decisioni su cosa fare se c'è un problema con il cast. Quindi, qui stai solo andando in crash se il cast fallisce. È meglio lasciare che il cliente decida cosa fare poiché questo può variare a seconda del cliente. Quindi, voterò questa risposta per questo motivo.
smileBot

@smileBot Vuoi dire che l'esempio è cattivo o l'approccio?
Orkhan Alikhanov

L'approccio. Penso che sia una regola generale di programmazione differire la specializzazione quando pratico. Questo fa parte dell'idea di intendere la responsabilità. Non è responsabilità della funzione specializzare il generico. Questa è la responsabilità del chiamante, poiché la funzione non può sapere di cosa avranno bisogno tutti i chiamanti. Se la funzione rientra in questo ruolo, questo limita l'uso della funzione per nessun guadagno. Puoi generalizzare questo a molti problemi di codifica. Questo unico concetto mi ha aiutato enormemente.
smileBot

4

Non hai bisogno di un generico qui poiché hai tipi statici (String come parametro), ma se vuoi che una funzione generica ne chiami un'altra puoi fare quanto segue.

Utilizzo di metodi generici

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
} 

Utilizzo di una classe generica (meno flessibile in quanto il generico può essere definito per 1 solo tipo per istanza)

class Foo<T> {

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

   func doOtherStuff(text: String) -> T {

   }  

}

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

3

Penso che quando specifichi una funzione generica dovresti specificare alcuni dei parametri di tipo T, come segue:

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

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

e se vuoi chiamare il metodo handle (), puoi farlo scrivendo protocollo e specificando il vincolo di tipo per 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)
}

quindi puoi chiamare questa funzione generica con String:

generic2("Some")

e verrà compilato


1

Finora, la mia migliore pratica personale ha utilizzato la risposta di @ orkhan-alikhanov. Oggi, guardando SwiftUI e come .modifier()e ViewModifierimplementato, ho trovato un altro modo (o è più una soluzione alternativa?)

Metti semplicemente la seconda funzione in un file struct .

Esempio:

Se questo ti dà il "Impossibile specializzare esplicitamente una funzione generica"

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

Questo potrebbe aiutare. Avvolgi la dichiarazione di generic1in un struct:

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

e chiamalo con:

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

Osservazioni:

  • Non so se aiuta in ogni caso possibile, quando si verifica questo messaggio di errore. So solo che sono rimasto bloccato molte volte quando è successo. So che questa soluzione ha aiutato oggi, quando è apparso il messaggio di errore.
  • Il modo in cui Swift gestisce i Generics è per me ancora fonte di confusione.
  • Questo esempio e la soluzione alternativa con structsono un buon esempio. La soluzione qui non ha un po 'più di informazioni, ma passa il compilatore. Stesse informazioni, ma risultati diversi? Allora qualcosa non va. Se si tratta di un bug del compilatore, può essere risolto.

0

Ho avuto un problema simile con la mia funzione di classe generica class func retrieveByKey<T: GrandLite>(key: String) -> T?.

Non potrei chiamarlo let a = retrieveByKey<Categories>(key: "abc")dove Categorie è una sottoclasse di GrandLite.

let a = Categories.retrieveByKey(key:"abc")ha restituito GrandLite, non Categorie. Le funzioni generiche non deducono il tipo in base alla classe che le chiama.

class func retrieveByKey<T: GrandLite>(aType: T, key: String>) -> T?mi ha dato un errore quando ho provato let a = Categories.retrieveByKey(aType: Categories, key: "abc")mi ha dato un errore che non poteva convertire Categories.Type in GrandLite, anche se Categories è una sottoclasse di GrandLite. PERÒ...

class func retrieveByKey<T: GrandLite>(aType: [T], key: String) -> T? ha funzionato se ho provato let a = Categories.retrieveByKey(aType: [Categories](), key: "abc")apparentemente un'assegnazione esplicita di una sottoclasse non funziona, ma un'assegnazione implicita che utilizza un altro tipo generico (array) funziona in Swift 3.


1
Per correggere l'errore, devi fornire aTypecome istanza di T, invece di inserire Categoriesse stesso. Es: let a = Categories.retrieveByKey(aType: Categories(), key: "abc"). Un'altra soluzione è definire aType: T.Type. Quindi chiama il metodo comelet a = Categories.retrieveByKey(aType: Categories.self, key: "abc")
nahung89
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.