Associated Type Constraints - wurzelsand/swift-memos GitHub Wiki
- Generics
- Schlüsselwort
some
Wir haben hier ein Protokoll für einen Container aus Integer-Elementen sowie eine Funktion die zwei solcher Container-Objekte auf Gleichheit prüft:
protocol IntContainer {
var element: Int { get }
}
func equal(a: IntContainer, b: IntContainer) -> Bool {
return a.element == b.element
}
Jetzt wollen wir einen Container entwickeln, dessen element
-Typ nicht auf Int
beschränkt sein soll. Wieder soll die Funktion equal
zwei solcher Container-Objekte auf Gleichheit prüfen:
protocol Container {
associatedtype Element: Equatable
var element: Element { get }
}
func equal(a: Container, b: Container) -> Bool {
return a.element == b.element
}
Warum ist die Funktion fehlerhaft? Wie müsste sie implementiert werden?
Der ersten equal
-Funktion mit IntContainer
-Parametern, können zwei unterschiedliche IntContainer
-Implementierungen übergeben werden. Da der element
-Typ aber in jedem Fall Int
ist, können die Parameter trotzdem auf Gleichheit überprüft werden.
Bei der zweiten equal
-Funktion mit Container
-Parametern ist zwar der Typ von element
auf Equatable
beschränkt. Diese Einschränkung genügt aber nicht, um beide Parameter vergleichbar zu machen. Die eine Container
-Implementierung könnte aus String
-Objekten, die andere aus Int
-Objekten bestehen. Und ein String
-Objekt ließe sich nicht mit einem Int
-Objekt vergleichen.
Daher:
protocol Container
associatedtype Element: Equatable
var element: Element { get }
}
func equal<T>(a: T, b: T) -> Bool where T: Container {
return a.element == b.element
}
Ich habe ein Protokoll sowie eine Implementierung des Protokolls:
protocol Wrapper {
associatedtype Type
var value: Type { get }
}
struct IntWrapper: Wrapper {
var value: Int
}
Ich möchte eine Funktion makeWrapper
schreiben, die einfach eine Instanz von IntWrapper
erzeugt und zurück gibt. Allerdings darf der Rückgabetyp von makeWrapper
nicht auf IntWrapper
beschränkt sein, um die Möglichkeit offen zu halten, die Implementierung von makeWrapper
später noch zu ändern und einen anderen konkreten Wrapper
-Typ zurückzugeben.
Diese beiden Implementierungen funktionieren nicht:
// Protocol 'Wrapper' can only be used as a generic constraint
// because it has Self or associated type requirements:
func makeWrapper() -> Wrapper {
return IntWrapper(value: 7)
}
func makeWrapper<T: Wrapper>() -> T {
// Cannot convert return expression of type 'IntWrapper'
// to return type 'T':
return IntWrapper(value: 7)
}
Daher:
func makeWrapper() -> some Wrapper {
return IntWrapper(value: 7)
}
-
some Wrapper
bedeutet: Die Funktion kann nur eine bestimmte Art vonWrapper
zurückgeben. So etwas wird z. B. verhindert:
// Function declares an opaque return type, but the return statements
// in its body do not have matching underlying types:
func makeWrapper() -> some Wrapper {
if Bool.random() {
return StringWrapper(value: "7")
}
return IntWrapper(value: 7)
}
- Ich glaube, dass eine Funktion immer nur dann ein Objekt eines Protokolls zurück geben kann, wenn dessen
associatedtype
zur Kompilierzeit ein konkreter Typ ist. - Ein opaque Type ist vor allem dadurch hilfreich, dass es nur verspricht ein Objekt zurückzugeben, das ein bestimmtes Protokoll erfüllt. Wie verschachtelt der Typ des Objekts tatsächlich ist, ist nebensächlich und kann sich auch, je nach Implementierung noch ändern. In SwiftUI erleichtert es die Deklaration von Views. Statt:
var body: VStack<TupleView<(Text, Text)>> {
VStack {
Text("City")
Text("New York")
}
}
reicht einfach:
var body: some View {
VStack {
Text("City")
Text("New York")
}
}