some Viewist ein undurchsichtiger Ergebnistyp, wie er von SE-0244 eingeführt wurde, und ist in Swift 5.1 mit Xcode 11 verfügbar. Sie können sich dies als einen "umgekehrten" generischen Platzhalter vorstellen.
Im Gegensatz zu einem normalen generischen Platzhalter, der vom Anrufer erfüllt wird:
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.
Ein undurchsichtiger Ergebnistyp ist ein impliziter generischer Platzhalter, der von der Implementierung erfüllt wird. Sie können sich also Folgendes vorstellen:
func bar() -> some P {
return S1() // Implementation chooses S1 for the opaque result.
}
so aussehend:
func bar() -> <Output : P> Output {
return S1() // Implementation chooses Output == S1.
}
Tatsächlich besteht das letztendliche Ziel dieser Funktion darin, umgekehrte Generika in dieser expliziteren Form zuzulassen, wodurch Sie auch Einschränkungen hinzufügen können, z -> <T : Collection> T where T.Element == Int. Weitere Informationen finden Sie in diesem Beitrag .
Die Hauptsache wegzunehmen ist , dass eine Funktion der Rückkehr some Pist ein , dass die Renditen ein Wert eines bestimmten Einzel konkreter dass Konform P. Der Versuch, verschiedene konforme Typen innerhalb der Funktion zurückzugeben, führt zu einem Compilerfehler:
// 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()
}
}
Da der implizite generische Platzhalter nicht von mehreren Typen erfüllt werden kann.
Dies steht im Gegensatz zu einer zurückgegebenen Funktion P, die verwendet werden kann, um beide darzustellen, S1und S2weil sie einen beliebigen Pkonformen Wert darstellt:
func baz(_ x: Int) -> P {
if x > 10 {
return S1()
} else {
return S2()
}
}
Okay, welche Vorteile haben undurchsichtige Ergebnistypen -> some Pgegenüber Protokollrückgabetypen -> P?
1. Undurchsichtige Ergebnistypen können mit PATs verwendet werden
Eine wesentliche derzeitige Einschränkung von Protokollen besteht darin, dass PATs (Protokolle mit zugehörigen Typen) nicht als tatsächliche Typen verwendet werden können. Obwohl dies eine Einschränkung ist, die wahrscheinlich in einer zukünftigen Version der Sprache aufgehoben wird, da undurchsichtige Ergebnistypen praktisch nur generische Platzhalter sind, können sie heute mit PATs verwendet werden.
Dies bedeutet, dass Sie Dinge tun können wie:
func giveMeACollection() -> some Collection {
return [1, 2, 3]
}
let collection = giveMeACollection()
print(collection.count) // 3
2. Undurchsichtige Ergebnistypen haben Identität
Da undurchsichtige Ergebnistypen erzwingen, dass ein einzelner konkreter Typ zurückgegeben wird, weiß der Compiler, dass zwei Aufrufe derselben Funktion zwei Werte desselben Typs zurückgeben müssen.
Dies bedeutet, dass Sie Dinge tun können wie:
// 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.
Das ist legal , da der Compiler weiß , dass beide xund yden gleichen Betontyp haben. Dies ist eine wichtige Voraussetzung für ==beide Parameter des Typs Self.
protocol Equatable {
static func == (lhs: Self, rhs: Self) -> Bool
}
Dies bedeutet, dass zwei Werte erwartet werden, die beide vom gleichen Typ sind wie der konkrete konforme Typ. Selbst wenn Sie Equatableals Typ verwendet werden könnten, könnten Sie nicht zwei beliebige Equatableübereinstimmende Werte miteinander vergleichen , zum Beispiel:
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.
Da der Compiler nicht beweisen kann, dass zwei beliebige EquatableWerte den gleichen zugrunde liegenden konkreten Typ haben.
In ähnlicher Weise, wenn wir eine andere undurchsichtige Rückgabefunktion vom Typ eingeführt haben:
// 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.
Das Beispiel wird illegal, weil obwohl beide foound barzurückkehren some Equatable, ihre "umgekehrten" generischen Platzhalter Output1und Output2von verschiedenen Typen erfüllt werden könnten.
3. Undurchsichtige Ergebnistypen bestehen aus generischen Platzhaltern
Im Gegensatz zu regulären protokolltypisierten Werten lassen sich undurchsichtige Ergebnistypen gut mit regulären generischen Platzhaltern kombinieren, zum Beispiel:
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.
Dies hätte nicht funktioniert, wenn makePes gerade zurückgegeben worden wäre P, da zwei PWerte unterschiedliche zugrunde liegende konkrete Typen haben können, zum Beispiel:
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.
Warum einen undurchsichtigen Ergebnistyp über dem Betontyp verwenden?
An diesem Punkt denken Sie sich vielleicht, warum schreiben Sie den Code nicht einfach wie folgt:
func makeP() -> S {
return S(i: 0)
}
Durch die Verwendung eines undurchsichtigen Ergebnistyps können Sie den Typ zu Seinem Implementierungsdetail machen P, indem Sie nur die von bereitgestellte Schnittstelle verfügbar machen. So können Sie den konkreten Typ später flexibel ändern, ohne den von der Funktion abhängigen Code zu beschädigen.
Zum Beispiel könnten Sie ersetzen:
func makeP() -> some P {
return S(i: 0)
}
mit:
func makeP() -> some P {
return T(i: 1)
}
ohne einen Code zu brechen, der aufruft makeP().
Siehe den Opaque Arten Abschnitt der Sprachführung und die Swift Evolution Vorschlag für weitere Informationen zu dieser Funktion.