Impossible de spécialiser explicitement une fonction générique

91

J'ai un problème avec le code suivant:

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

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

le résultat generic1 (nom) à l'erreur du compilateur "Impossible de spécialiser explicitement une fonction générique"

Y a-t-il un moyen d'éviter cette erreur? Je ne peux pas changer la signature de la fonction generic1, donc ça devrait être (String) -> Void

Greyisf
la source
2
Quel est l'intérêt d'utiliser le type générique ici quand il ne peut pas être déduit pour le contexte? Si le type générique est utilisé uniquement en interne, vous devez spécifier le type dans le corps de la fonction.
Kirsteins
Le type d'espace réservé est-il Tutilisé dans generic1()? Comment voulez - vous appeler cette fonction, de sorte que le compilateur peut déduire le type?
Martin R
3
J'espère qu'il existe un moyen d'appeler une fonction telle que gene1 <blaClass> ("SomeString")
Greyisf
1
Les génériques ne sont pas uniquement destinés aux cas où le compilateur peut déduire le contexte. La spécialisation explicite de la fonction permettrait d'inférer d'autres parties du code. ex: func foo<T>() -> T { ... }ça fait quelque chose avec un objet tde type Tet le return t. Une spécialisation explicite Tpermettrait t1d'être impliqué var t1 = foo<T>(). J'aurais aimé qu'il y ait un moyen d'appeler des fonctions de cette façon aussi. C # le permet
nacho4d
1
Je suis juste tombé sur ça aussi. Cela n'a aucun sens de créer une fonction générique si vous êtes obligé de passer le type en tant que paramètre. Cela doit être un bug, non?!
Nick

Réponses:

160

J'ai également eu ce problème et j'ai trouvé une solution de contournement pour mon cas.

Dans cet article, l'auteur a le même problème

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

Le problème semble donc être que le compilateur doit en quelque sorte déduire le type de T. Mais il n'est pas permis d'utiliser simplement le <type> générique (params ...).

Normalement, le compilateur peut rechercher le type de T, en analysant les types de paramètres car c'est là que T est utilisé dans de nombreux cas.

Dans mon cas, c'était un peu différent, car le type de retour de ma fonction était T. Dans votre cas, il semble que vous n'ayez pas du tout utilisé T dans votre fonction. Je suppose que vous venez de simplifier l'exemple de code.

J'ai donc la fonction suivante

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

Et en cas de, par exemple

getProperty<Int>("countProperty")

le compilateur me donne l'erreur:

Impossible de spécialiser explicitement une fonction générique

Ainsi, pour donner au compilateur une autre source d'informations pour déduire le type de T, vous devez déclarer explicitement le type de la variable dans laquelle la valeur de retour est enregistrée.

var value : Int = getProperty("countProperty")

De cette façon, le compilateur sait que T doit être un entier.

Donc, je pense que dans l'ensemble, cela signifie simplement que si vous spécifiez une fonction générique, vous devez au moins utiliser T dans vos types de paramètres ou comme type de retour.

ThottChief
la source
2
Cela m'a fait gagner beaucoup de temps. Je vous remercie.
chrislarson
3
Existe-t-il un moyen de le faire s'il n'y a pas de valeur de retour? ie,func updateProperty<T>( propertyID : String )
Kyle Bashour
1
Je ne vois pas pourquoi tu voudrais faire ça? Puisque vous ne retournez rien, il n'y a aucun avantage à déclarer votre fonction générique.
ThottChief
@ThottChief il y a beaucoup d'avantages, par exemple si vous utilisez / construisez des chemins d'accès, ou obtenez le nom du type sous forme de chaîne, etc.
zaitsman
Excellente réponse merci. Je souhaite que cela fonctionne avec let value = foo() as? Typedonc il pourrait être utilisé dans un ifou si guardle résultat est optionnel, mais ce n'est pas le cas ...
agirault
53

Swift 5

En règle générale, il existe de nombreuses façons de définir des fonctions génériques. Mais ils sont basés sur des conditions qui Tdoivent être utilisées en tant que parameter, ou en tant que 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
    }
}

Après cela, il faut prévoir Tlors de l'appel.

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

Réf:

  1. http://austinzheng.com/2015/01/02/swift-generics-pt-1/
  2. https://dispatchswift.com/type-constraints-for-generics-in-swift-d6bf2f0dbbb2
nahung89
la source
Excellente réponse au problème général ... car il y a tellement de façons de le résoudre. J'ai également réalisé des self.result = UIViewController.doSomething()travaux seuls tant que vous avez tapé la propriété lorsque vous la déclarez.
teradyl
excellente réponse expliquant beaucoup de choses.
Okhan Okbay
Java doLastThing<UITextView>()devient Swift doLastThing(UITextView.self), ce qui n'est ... pas le pire, du moins. Mieux que d'avoir à taper explicitement des résultats compliqués. Merci pour la solution de contournement.
Erhannis le
18

La solution prend le type de classe comme paramètre (comme en Java)

Pour indiquer au compilateur de quel type il a affaire, passez la classe en argument

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

Appelez en tant que:

self.navigate(UserDetailsViewController.self, id: "UserDetailsViewController", before: {
        controller in
        controller.user = self.notification.sender
    })
Orkhan Alikhanov
la source
1
C'est génial et bien meilleur que la réponse acceptée. Veuillez envisager de modifier pour supprimer la partie historique, ou au moins la mettre sous la solution. Merci!
Dan Rosenstark
1
@DanRosenstark Merci pour les commentaires :)
Orkhan Alikhanov
Bien que cela fonctionne, je ne pense pas que ce soit la meilleure pratique. La raison en est que maintenant, la définition de la fonction prend des décisions sur ce qu'il faut faire en cas de problème avec le casting. Donc, ici, vous vous écrasez si le casting échoue. Il vaut mieux laisser le client décider quoi faire car cela peut varier selon le client. Donc, je vais voter cette réponse pour cette raison.
smileBot
@smileBot Vous voulez dire que l'exemple est mauvais ou l'approche?
Orkhan Alikhanov
L'approche. Je pense que c'est une règle générale de la programmation de reporter la spécialisation lorsque cela est possible. Cela fait partie de l'idée de comprendre la responsabilité. Il n'est pas de la responsabilité de la fonction de spécialiser le générique. C'est la responsabilité de l'appelant, car la fonction ne peut pas savoir ce dont tous les appelants auront besoin. Si la fonction entre dans ce rôle, cela limite l'utilisation de la fonction sans gain. Vous pouvez généraliser cela à de nombreux problèmes de codage. Ce concept unique m'a énormément aidé.
smileBot
4

Vous n'avez pas besoin d'un générique ici car vous avez des types statiques (String en tant que paramètre), mais si vous voulez qu'une fonction générique en appelle une autre, vous pouvez faire ce qui suit.

Utilisation de méthodes génériques

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
} 

Utilisation d'une classe générique (moins flexible car le générique peut être défini pour 1 type uniquement par 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
la source
3

Je pense que lorsque vous spécifiez une fonction générique, vous devez spécifier certains des paramètres de type T, comme suit:

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

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

et si vous voulez appeler la méthode handle (), vous pouvez le faire en écrivant le protocole et en spécifiant la contrainte de type pour 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)
}

vous pouvez donc appeler cette fonction générique avec String:

generic2("Some")

et il compilera

Valerii Lider
la source
1

Jusqu'à présent, mes meilleures pratiques personnelles ont utilisé la réponse de @ orkhan-alikhanov. Aujourd'hui, quand on regarde SwiftUI et comment .modifier()et ViewModifierest mis en œuvre, j'ai trouvé une autre façon (ou est - il plus une solution de contournement?)

Enveloppez simplement la deuxième fonction dans un fichier struct.

Exemple:

Si celui-ci vous donne le "Impossible de spécialiser explicitement une fonction générique"

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

Celui-ci pourrait aider. Enveloppez la déclaration de generic1dans un struct:

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

et appelez-le avec:

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

Remarques:

  • Je ne sais pas si cela aide dans tous les cas possibles, lorsque ce message d'erreur se produit. Je sais juste que j'ai été coincé plusieurs fois lorsque cela s'est produit. Je sais que cette solution a aidé aujourd'hui, lorsque le message d'erreur est apparu.
  • La façon dont Swift gère les génériques est pour moi encore déroutante.
  • Cet exemple et la solution de contournement avec le structest un bon exemple. La solution de contournement ici n'a pas un peu plus d'informations - mais passe le compilateur. Mêmes informations, mais résultats différents? Alors quelque chose ne va pas. S'il s'agit d'un bogue du compilateur, il peut être corrigé.
jboi
la source
0

J'ai eu un problème similaire avec ma fonction de classe générique class func retrieveByKey<T: GrandLite>(key: String) -> T?.

Je ne pourrais pas l'appeler là let a = retrieveByKey<Categories>(key: "abc")où Catégories est une sous-classe de GrandLite.

let a = Categories.retrieveByKey(key:"abc")renvoyé GrandLite, pas Catégories. Les fonctions génériques ne déduisent pas de type en fonction de la classe qui les appelle.

class func retrieveByKey<T: GrandLite>(aType: T, key: String>) -> T?m'a donné une erreur lorsque j'ai essayé let a = Categories.retrieveByKey(aType: Categories, key: "abc")m'a donné une erreur qu'il ne pouvait pas convertir Categories.Type en GrandLite, même si Categories est une sous-classe de GrandLite. TOUTEFOIS...

class func retrieveByKey<T: GrandLite>(aType: [T], key: String) -> T? a fonctionné si j'essayais let a = Categories.retrieveByKey(aType: [Categories](), key: "abc")apparemment une affectation explicite d'une sous-classe ne fonctionne pas, mais une affectation implicite utilisant un autre type générique (tableau) fonctionne dans Swift 3.

adamek
la source
1
Pour corriger l'erreur, vous devez fournir aTypecomme instance de T, au lieu de se mettre Categories. Ex: let a = Categories.retrieveByKey(aType: Categories(), key: "abc"). Une autre solution est de définir aType: T.Type. Ensuite, appelez la méthode commelet a = Categories.retrieveByKey(aType: Categories.self, key: "abc")
nahung89