Dlaczego typy powiązane dla protokołów nie używają składni typu generycznego w języku Swift?

Jestem zdezorientowany co do różnicy między składnią używaną dla typów powiązanych dla protokołów, z jednej strony, a typami generycznymi z drugiej.

W języku Swift, na przykład, można zdefiniować typ generyczny używając czegoś takiego jak

struct Stack<T> {
    var items = [T]()
    mutating func push(item: T) {
        items.append(item)
    }
    mutating func pop() -> T {
        return items.removeLast()
    }
}

Podczas gdy definiuje się protokół z powiązanymi typami używając czegoś w rodzaju

protocol Container {
    associatedtype T
    mutating func append(item: T)
    var count: Int { get }
    subscript(i: Int) -> T { get }
}

Dlaczego to drugie nie jest tylko:

protocol Container<T> {
    mutating func append(item: T)
    var count: Int { get }
    subscript(i: Int) -> T { get }
}

Czy Jest jakiś głęboki (a może po prostu oczywisty i zagubiony dla mnie) powód, dla którego język nie przyjął ta druga składnia?

Author: c0ming, 2014-10-24

2 answers

Zostało to omówione kilka razy na liście deweloperów. Podstawową odpowiedzią jest to, że powiązane typy są bardziej elastyczne niż parametry typu. Chociaż masz tu konkretny przypadek jednego parametru typu, całkiem możliwe jest posiadanie kilku. Na przykład zbiory mają Typ elementu, ale także typ indeksu i typ generatora. Jeśli specjalizujesz je całkowicie z parametryzacją typu, musisz mówić o rzeczach takich jak Array<String, Int, Generator<String>> lub tym podobnych. (Umożliwiłoby mi to tworzenie tablic, które były subscripted przez coś innego niż Int, co można uznać za funkcję, ale również dodaje wiele złożoności.)

Można to wszystko pominąć (robi to Java), ale wtedy masz mniej sposobów, aby ograniczyć swoje typy. Java w rzeczywistości jest dość ograniczona w tym, jak może ograniczać typy. Nie możesz mieć dowolnego typu indeksowania w kolekcjach w języku Java. Scala rozszerza system typów Javy o powiązane typy, tak jak Swift. Powiązane typy były niezwykle potężne w Scali. Są również stałym źródłem dezorientacji i łzawienia włosów.

Czy ta dodatkowa moc jest tego warta, to zupełnie inne pytanie, a czas pokaże. Ale powiązane typy zdecydowanie są potężniejsze niż prosta parametryzacja typu.

 42
Author: Rob Napier,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-12-03 19:50:26

Odpowiedź Robnapiera jest (jak zwykle) całkiem dobra, ale tylko dla alternatywnej perspektywy, która może okazać się jeszcze bardziej pouczająca...

Na Powiązanych Typach

Protokół jest abstrakcyjnym zbiorem wymagań - listą kontrolną, którą konkretny typ musi spełnić, aby powiedzieć, że jest zgodny z protokołem. Tradycyjnie myśli się o tej liście zachowań: metodach lub właściwościach zaimplementowanych przez konkretny typ. Powiązane typy są sposobem nazywania rzeczy, które są udział w takiej liście kontrolnej, a tym samym rozszerzenie definicji, zachowując ją otwartą, aby Jak zgodny typ implementuje zgodność.

Kiedy widzisz:

protocol SimpleSetType {
    associatedtype Element
    func insert(_ element: Element)
    func contains(_ element: Element) -> Bool
    // ...
}

Oznacza to, że aby Typ twierdził zgodność z SimpleSetType, nie tylko ten typ musi zawierać funkcje insert(_:) i contains(_:), te dwie funkcje muszą przyjmować ten sam typ parametru, co każdy inny. Ale nie ma znaczenia, jaki jest typ tego parametru.

Możesz zaimplementować to protokół z typem generycznym lub niegenerycznym:

class BagOfBytes: SimpleSetType {
    func insert(_ byte: UInt8) { /*...*/ }
    func contains(_ byte: UInt8) -> Bool { /*...*/ }
}

struct SetOfEquatables<T: Equatable>: SimpleSetType {
    func insert(_ item: T) { /*...*/ }
    func contains(_ item: T) -> Bool { /*...*/ }
}    

Zauważ, że nigdzie BagOfBytes lub SetOfEquatables nie definiują połączenia pomiędzy SimpleSetType.Element a typem używanym jako parametr dla ich dwóch metod - kompilator automagicznie sprawdza, że typy te są powiązane z odpowiednimi metodami, więc spełniają wymagania protokołu dla skojarzonego typu.

Na Parametrach Typu Ogólnego

Gdzie powiązane typy rozszerzają słownictwo do tworzenia abstrakcyjnych list kontrolnych, generycznych parametry typu ograniczają implementację konkretnego typu. Jeśli masz klasę generyczną taką jak ta:

class ViewController<V: View> {
    var view: V
}

To nie mówi, że istnieje wiele różnych sposobów, aby ViewController (tak długo, jak masz view), to mówi, że ViewController jest prawdziwą, konkretną rzeczą i ma view. Co więcej, nie wiemy dokładnie, jaki Widok ma dana instancja ViewController, ale wiemy, że musi to być View (albo podklasa klasy View, albo Typ wdrożenie protokołu {[16] }... nie mówimy).

Lub inaczej mówiąc, pisanie typu ogólnego lub funkcji jest rodzajem skrótu do pisania rzeczywistego kodu. Weźmy ten przykład:

func allEqual<T: Equatable>(a: T, b: T, c: T) {
    return a == b && b == c
}

To ma taki sam efekt, jakbyś przejrzał wszystkie typy Equatable i napisał:

func allEqual(a: Int, b: Int, c: Int) { return a == b && b == c }
func allEqual(a: String, b: String, c: String) { return a == b && b == c }
func allEqual(a: Samophlange, b: Samophlange, c: Samophlange) { return a == b && b == c }

Jak widzisz, tworzymy kod tutaj, implementując nowe zachowanie-znacznie w przeciwieństwie do typów powiązanych z protokołem, gdzie opisujemy tylko wymagania dla czegoś Inne do spełnienia.

TLDR

Typy powiązane i parametry typów generycznych są bardzo różnymi rodzajami narzędzi: typy powiązane są językiem opisu, a generyki są językiem implementacji. Mają bardzo różne cele, nawet jeśli ich zastosowania czasami wyglądają podobnie (zwłaszcza jeśli chodzi o subtelne na pierwszy rzut oka różnice, takie jak między abstrakcyjnym schematem dla kolekcji dowolnego typu elementu, a rzeczywistym typem kolekcji, który może nadal mieć dowolne element ogólny). Ponieważ są bardzo różnymi bestiami, mają inną składnię.

Czytaj dalej

Zespół Swift ma ładny writeup na generyki, protokoły i powiązane funkcje tutaj .

 44
Author: rickster,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-02-03 05:11:51