Le paramètre générique 'T' n'a pas pu être déduit après l'affectation
// Xcode 11.6 / Swift 5
import Foundation
func f<T>(_: (T) -> Void) { }
@discardableResult
func g(_: Int) -> Int { 0 }
f { g($0) } // compiles fine
f { let _ = g($0) } // Generic parameter 'T' could not be inferred
Dans le code ci-dessus, la fonction générique f
attend comme argument une fonction qui prend un argument de type T
.
La fonction g
prend un argument de type Int
.
Quand j'écris f { g($0) }
, le code compile. Je crois (corrigez-moi si je me trompe) que cela compile parce que le compilateur peut en déduire qu'il T
s'agit d'un type d'argument Int
basé sur .g
Cependant, lorsque j'essaie de faire quelque chose avec la valeur de retour de g
, par exemple dans la let _ = g($0)
ligne, le compilateur se plaint de ne plus pouvoir déduire le type de T
.
Il me semble que le type de retour de g
ne devrait pas avoir d'incidence sur la façon dont le compilateur déduit T
le type de , mais c'est clairement le cas.
Quelqu'un peut-il expliquer pourquoi cela se produit et ce qui peut être fait (le cas échéant) pour le corriger?
Réponses
Cela peut ou non être un bogue du compilateur.
On sait que Swift n'essaie pas de déduire les types de certaines fermetures, à savoir les fermetures multi-instructions, comme indiqué dans SR-1570 :
C'est un comportement correct : Swift ne déduit pas les types de paramètre ou de retour à partir du corps des fermetures à plusieurs instructions.
Cependant, votre fermeture consiste en une seule déclaration, une déclaration pour être précis. Il est possible, bien que bizarre, qu'ils l'aient conçu pour que Swift n'essaie pas de déduire les types si la fermeture contient également une déclaration . Par exemple, cela ne compile pas non plus
f { let x: Int = $0 } // nothing to do with "g"! The issue seems to be with declarations
Si c'était par conception, la raison derrière cela pourrait être parce qu'une seule déclaration dans une fermeture n'a pas beaucoup de sens de toute façon. Tout ce qui est déclaré ne sera pas utilisé.
Mais encore une fois, ce ne sont que des spéculations, et cela pourrait aussi être un bogue.
Pour résoudre ce problème, faites-en simplement une non-déclaration :
f { _ = g($0) } // this, without the "let", is IMO the idiomatic way to ignore the function result
Ou
f { g($0) } // g has @discardableResult anyway, so you don't even need the wildcard
La fonction f
prend une fonction en paramètre qui à son tour prend un paramètre de type T
et ne renvoie rien ( Void
). Pour qu'une fermeture infère automatiquement des types, elle doit consister en une expression unique (et parfois simple). Tout ce qui est complexe rend difficile la déduction pour le compilateur (ce qui est logique du point de vue du compilateur). Apparemment, let _ = g($0)
est une déclaration complexe en ce qui concerne le compilateur. Pour plus d'informations, consultez cette discussion sur la liste de diffusion
il semble que @discardableResult vous donne la possibilité d'avoir 2 types de fonctions :
g(_: Int) -> Int et g(_: Int) -> Void (c'est quand vous ne voulez pas utiliser un résultat de fonction)
je pense que
f { g($0) }
- ici votre f peut déduire un type car il a le même type
(_: (T) -> Void) and (_: (Int) -> Void)
f { let _ = g($0) }
- dans ce cas le type de la fonction g est différent de la fonction f
(_: (T) -> Void) and (_: (Int) -> Int)
Si vous supprimez "let", il compilera à nouveau :
f { _ = g($0) }
Je pense que f { let _ = g($0) }
- renvoie uniquement la valeur Int f { _ = g($0) }
- renvoie la fonction (_: (Int) -> Int)
Peut-être que c'est une clé ici