some Viewest 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 Pest 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 S2parce qu'elle représente une Pvaleur 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 Pretour 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 xet yont 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 Equatablevaleurs 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 Equatablevaleurs 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 fooet le barretour some Equatable, leur « inverse » génériques des espaces réservés Output1et Output2pourrait ê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 makePvenait de revenir P, car deux Pvaleurs 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é.