1
votes

Utiliser Type Erasure return Generic Type dans une fonction avec Swift (Impossible de convertir l'expression de retour de type…)

J'ai un problème avec les génériques dans Swift. Exposons mon code.

protocol FooProtocol {
    associatedtype T
}

protocol Fooable { }
extension Int : Fooable { }
extension String: Fooable { }

class AnyFoo<T>: FooProtocol {
    init<P: FooProtocol>(p: P) where P.T == T { }
}

class FooIntImpClass: FooProtocol {
    typealias T = Int
}

class FooStringImpClass: FooProtocol {
    typealias T = String
}

func createOne(isInt: Bool) -> AnyFoo<Fooable> {
    if isInt {
        let anyFoo = AnyFoo(p: FooIntImpClass())
          return anyFoo
    } else {
        let anyFoo = AnyFoo(p: FooStringImpClass())
        return anyFoo
    }
}

func createTwo<F: Fooable>(isInt: Bool) -> AnyFoo<F> {
    if isInt {
        let anyFoo = AnyFoo(p: FooIntImpClass())
          return anyFoo
    } else {
        let anyFoo = AnyFoo(p: FooStringImpClass())
        return anyFoo
    }
}

createOne a obtenu une erreur

Impossible de convertir l'expression de retour de type 'AnyFoo' (alias 'AnyFoo') en type de retour 'AnyFoo'

createTwo a obtenu une erreur

Impossible de convertir l'expression de retour de type 'AnyFoo' (alias 'AnyFoo') en type de retour 'AnyFoo'

Pourquoi cela se produit-il? Je renvoie la valeur correcte.

Et quelle est la différence avec createOne et createTwo


4 commentaires

Vous ne renvoyez pas la valeur correcte. Vous déclarez que createOne renvoie AnyFoo

, mais vous retournez toujours le même type, AnyFoo , donc la fonction est pas générique. Qu'est-ce que vous essayez réellement d'accomplir avec votre fonction createOne ?


Le code a une erreur, changez le createOne en code suivant, a également l'erreur func createOne () -> AnyFoo {let anyFoo = AnyFoo (p: FooImpClass ()) renvoie anyFoo }


Si vous utilisez Any dans un type générique, vous abusez gravement des génériques. Cela ne fonctionnera jamais non plus, car les types génériques Swift sont invariants - AnyFoo est complètement indépendant de tous les autres types AnyFoo , tels que AnyFoo < / code>, même si Any peut être utilisé pour représenter tous les types Swift.


J'ai modifié ma réponse pour répondre à vos modifications. Notez que vous ne devez pas modifier votre question de manière telle qu'elle invalide les réponses existantes.


3 Réponses :


1
votes

MODIFIER pour répondre à la modification de la question:

createTwo ne fonctionne pas car vous avez la même idée fausse que celle que j'ai dite dans ma réponse d'origine. createTwo a décidé de son propre chef que F devrait être soit String ou Int , plutôt que «tout type conforme à Fooable".

Pour createOne , vous avez une autre idée fausse courante. Les classes génériques sont invariantes . AnyFoo n'est pas une sorte de AnyFoo . En fait, ce sont des types totalement indépendants! Consultez ici pour plus de détails.

En gros, ce que vous essayez ne viole pas la sécurité des types, et vous redéfinissez vos API et choisissez une autre approche différente.


Réponse originale (pour la révision initiale de la question)

Vous semblez avoir une idée fausse des génériques. Les paramètres génériques sont décidés par l'appelant, pas par l'appelé.

Dans createOne , vous renvoyez anyFoo , qui est de type AnyFoo , et non AnyFoo

. La méthode (appelée) a décidé, d'elle-même, que P devrait être Int . Cela ne devrait pas arriver, car l ' appelant décide quels paramètres génériques devraient être. Si l'appelé est générique, il doit pouvoir travailler avec n'importe quel type (dans les limites des contraintes). Quoi qu'il en soit, P ne peut pas être Int ici de toute façon, puisque P: FooProtocol .

Votre createOne ne doit pas du tout être générique, car elle ne fonctionne qu'avec Int:

func createOne() -> AnyFoo<Int> {
    let anyFoo = AnyFoo(p: FooImpClass())
    return anyFoo
}


0 commentaires

1
votes

Est-ce que ce que vous avez essayé de réaliser est-il ce qui suit? (compilé et testé avec Xcode 11.4)

func createOne() -> some FooProtocol {
    let anyFoo = AnyFoo(p: FooImpClass())
    return anyFoo
}


2 commentaires

Bien, j'oublie toujours les types opaques


À l'avenir, j'ajouterai le type String , Double , etc. les types opaques ne le supportent pas.



0
votes

MODIFIER J'ai finalement réussi à conserver votre clause where :)

EDIT Je ne sais toujours pas ce que vous voulez faire, et je suis toujours d'accord avec @Sweeper mais j'adore abuser des génériques :):

protocol FooProtocol {
    associatedtype T
}

class AnyFoo<T>: FooProtocol where T: FooProtocol {
    init<P: FooProtocol>(p: P) { }
}

class FooImpClass: FooProtocol {
    typealias T = Int
}

func createOne<P: FooProtocol>() -> AnyFoo<P> {
    let anyFoo: AnyFoo<P> = AnyFoo(p: FooImpClass())
    return anyFoo
}

qui compile mais je ne sais pas quoi à voir avec ça.

Modifier

ouais je ne sais vraiment pas:

let one = createOne(foo: FooStringImpClass.self) // AnyFoo<String>
print(type(of: one).T) // "String\n"
let two = createTwo(foo: FooIntImpClass.self) // AnyFoo<Int>
print(type(of: two).T) // "Int\n"

Est-ce ce que vous vouliez?


Je ne suis pas sûr de ce que vous voulez en faire, mais je vous suggère d'essayer de mettre une clause where sur la classe AnyFoo au lieu de son initialiseur. Et je devrais ajouter que votre clause where sur l'initialiseur était fausse, comme Sweeper dit:

De toute façon, P ne peut pas être Int ici de toute façon, puisque P: FooProtocol.

Le code suivant se compile:

protocol FooProtocol {
    associatedtype T
    init()
}

protocol Fooable { }
extension Int : Fooable { }
extension String: Fooable { }

class AnyFoo<T>: FooProtocol {
    init<P: FooProtocol>(p: P) where P.T == T { }
    init<T>(p: T.Type) { }
    required init() { }
}

class FooIntImpClass: FooProtocol {
    typealias T = Int
    required init() { }
}

class FooStringImpClass: FooProtocol {
    typealias T = String
    required init() { }
}


func createOne<F: FooProtocol>(foo: F.Type) -> AnyFoo<F.T> {
    let anyFoo = AnyFoo<F.T>(p: F.init())
        return anyFoo
}

func createTwo<F: FooProtocol>(foo: F.Type) -> some FooProtocol {
    let anyFoo = AnyFoo<F.T>(pk: F.T.self)
    return anyFoo
}


0 commentaires