some View
ist 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 P
ist 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, S1
und S2
weil sie einen beliebigen P
konformen Wert darstellt:
func baz(_ x: Int) -> P {
if x > 10 {
return S1()
} else {
return S2()
}
}
Okay, welche Vorteile haben undurchsichtige Ergebnistypen -> some P
gegenü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 x
und y
den 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 Equatable
als 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 Equatable
Werte 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 foo
und bar
zurückkehren some Equatable
, ihre "umgekehrten" generischen Platzhalter Output1
und Output2
von 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 makeP
es gerade zurückgegeben worden wäre P
, da zwei P
Werte 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 S
einem 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.