Associated Type Constraints - wurzelsand/swift-memos GitHub Wiki

Associated Type Constraints

Themen

  • Generics
  • Schlüsselwort some

Aufgabe 1

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?

Ausführung

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
}

Aufgabe 2

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.

Ausführung

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 von Wrapper 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")
    }
}
⚠️ **GitHub.com Fallback** ⚠️