some View
est un type de résultat opaque introduit par SE-0244 et est disponible dans Swift 5.1 avec Xcode 11. Vous pouvez le considérer comme un espace réservé générique "inversé".
Contrairement à un espace réservé générique régulier qui est satisfait par l'appelant:
protocol P {}
struct S1 : P {}
struct S2 : P {}
func foo<T : P>(_ x: T) {}
foo(S1()) // Caller chooses T == S1.
foo(S2()) // Caller chooses T == S2.
Un type de résultat opaque est un espace réservé générique implicite satisfait par l' implémentation , vous pouvez donc y penser:
func bar() -> some P {
return S1() // Implementation chooses S1 for the opaque result.
}
comme ressemblant à ceci:
func bar() -> <Output : P> Output {
return S1() // Implementation chooses Output == S1.
}
En fait, l'objectif final de cette fonctionnalité est d'autoriser les génériques inversés sous cette forme plus explicite, ce qui vous permettrait également d'ajouter des contraintes, par exemple -> <T : Collection> T where T.Element == Int
. Voir cet article pour plus d'informations .
La principale chose à retenir est qu'une fonction renvoyant some P
est une fonction qui renvoie une valeur d'un type concret unique conforme P
. Tenter de renvoyer différents types conformes dans la fonction génère une erreur de compilation:
// error: Function declares an opaque return type, but the return
// statements in its body do not have matching underlying types.
func bar(_ x: Int) -> some P {
if x > 10 {
return S1()
} else {
return S2()
}
}
Comme l'espace réservé générique implicite ne peut pas être satisfait par plusieurs types.
Cela contraste avec une fonction renvoyant P
, qui peut être utilisée pour représenter les deux S1
et S2
parce qu'elle représente une P
valeur de conformité arbitraire :
func baz(_ x: Int) -> P {
if x > 10 {
return S1()
} else {
return S2()
}
}
D'accord, quels sont les avantages des types de résultats opaques par rapport aux types de -> some P
retour de protocole -> P
?
1. Les types de résultats opaques peuvent être utilisés avec les PAT
Une limitation actuelle majeure des protocoles est que les PAT (protocoles avec des types associés) ne peuvent pas être utilisés comme types réels. Bien qu'il s'agisse d'une restriction qui sera probablement levée dans une future version du langage, car les types de résultats opaques ne sont en fait que des espaces réservés génériques, ils peuvent être utilisés avec les PAT aujourd'hui.
Cela signifie que vous pouvez faire des choses comme:
func giveMeACollection() -> some Collection {
return [1, 2, 3]
}
let collection = giveMeACollection()
print(collection.count) // 3
2. Les types de résultats opaques ont une identité
Étant donné que les types de résultats opaques appliquent un seul type concret est renvoyé, le compilateur sait que deux appels à la même fonction doivent renvoyer deux valeurs du même type.
Cela signifie que vous pouvez faire des choses comme:
// foo() -> <Output : Equatable> Output {
func foo() -> some Equatable {
return 5 // The opaque result type is inferred to be Int.
}
let x = foo()
let y = foo()
print(x == y) // Legal both x and y have the return type of foo.
Ceci est légal car le compilateur sait que les deux x
et y
ont le même type concret. Il s'agit d'une exigence importante pour ==
, où les deux paramètres de type Self
.
protocol Equatable {
static func == (lhs: Self, rhs: Self) -> Bool
}
Cela signifie qu'il attend deux valeurs qui sont à la fois du même type que le type conforme au béton. Même s'ils Equatable
étaient utilisables comme type, vous ne seriez pas en mesure de comparer deux Equatable
valeurs conformes arbitraires , par exemple:
func foo(_ x: Int) -> Equatable { // Assume this is legal.
if x > 10 {
return 0
} else {
return "hello world"
}
}
let x = foo(20)
let y = foo(5)
print(x == y) // Illegal.
Comme le compilateur ne peut pas prouver que deux Equatable
valeurs arbitraires ont le même type concret sous-jacent.
De manière similaire, si nous introduisions une autre fonction de retour de type opaque:
// foo() -> <Output1 : Equatable> Output1 {
func foo() -> some Equatable {
return 5 // The opaque result type is inferred to be Int.
}
// bar() -> <Output2 : Equatable> Output2 {
func bar() -> some Equatable {
return "" // The opaque result type is inferred to be String.
}
let x = foo()
let y = bar()
print(x == y) // Illegal, the return type of foo != return type of bar.
L'exemple devient illégale parce que même si les deux foo
et le bar
retour some Equatable
, leur « inverse » génériques des espaces réservés Output1
et Output2
pourrait être satisfaite par différents types.
3. Les types de résultats opaques composent avec des espaces réservés génériques
Contrairement aux valeurs de type protocole standard, les types de résultats opaques se composent bien avec des espaces réservés génériques réguliers, par exemple:
protocol P {
var i: Int { get }
}
struct S : P {
var i: Int
}
func makeP() -> some P { // Opaque result type inferred to be S.
return S(i: .random(in: 0 ..< 10))
}
func bar<T : P>(_ x: T, _ y: T) -> T {
return x.i < y.i ? x : y
}
let p1 = makeP()
let p2 = makeP()
print(bar(p1, p2)) // Legal, T is inferred to be the return type of makeP.
Cela n'aurait pas fonctionné s'il makeP
venait de revenir P
, car deux P
valeurs peuvent avoir différents types concrets sous-jacents, par exemple:
struct T : P {
var i: Int
}
func makeP() -> P {
if .random() { // 50:50 chance of picking each branch.
return S(i: 0)
} else {
return T(i: 1)
}
}
let p1 = makeP()
let p2 = makeP()
print(bar(p1, p2)) // Illegal.
Pourquoi utiliser un type de résultat opaque sur le type de béton?
À ce stade, vous pensez peut-être à vous-même, pourquoi ne pas simplement écrire le code comme suit:
func makeP() -> S {
return S(i: 0)
}
Eh bien, l'utilisation d'un type de résultat opaque vous permet de faire le type S
un détail d'implémentation en exposant uniquement l'interface fournie parP
, ce qui vous donne la possibilité de changer le type concret plus tard sur la ligne sans casser le code qui dépend de la fonction.
Par exemple, vous pouvez remplacer:
func makeP() -> some P {
return S(i: 0)
}
avec:
func makeP() -> some P {
return T(i: 1)
}
sans casser aucun code qui appelle makeP()
.
Voir la section Types opaques du guide de langue et la proposition d'évolution Swift pour plus d'informations sur cette fonctionnalité.