Protokół nie jest zgodny z samym sobą?


125

Dlaczego ten kod Swift nie kompiluje się?

protocol P { }
struct S: P { }

let arr:[P] = [ S() ]

extension Array where Element : P {
    func test<T>() -> [T] {
        return []
    }
}

let result : [S] = arr.test()

Kompilator mówi: „Typ Pnie jest zgodny z protokołem P” (lub, w późniejszych wersjach Swift, „Używanie 'P' jako konkretnego typu zgodnego z protokołem 'P' nie jest obsługiwane.”).

Dlaczego nie? Jakoś wydaje się, że to dziura w języku. Zdaję sobie sprawę, że problem wynika z zadeklarowania tablicy arrjako tablicy typu protokołu , ale czy jest to nierozsądne? Myślałem, że protokoły są właśnie po to, aby pomóc strukturom w czymś w rodzaju hierarchii typów?


1
Po usunięciu adnotacji typu w let arrwierszu kompilator wnioskuje do typu [S]i kompiluje kod. Wygląda na to, że typu protokołu nie można używać w taki sam sposób, jak relacji klasa - superklasa.
vadian

1
@vadian Poprawnie, do tego właśnie odnosiłem się w swoim pytaniu, kiedy powiedziałem „Zdaję sobie sprawę, że problem wynika z zadeklarowania tablicy arr jako tablicy typu protokołu”. Ale, jak dalej mówię w moim pytaniu, głównym celem protokołów jest zwykle to, że mogą być one używane w taki sam sposób, jak relacja klasa - nadklasa! Są one przeznaczone do zapewnienia pewnego rodzaju struktury hierarchicznej do świata elemencie. I zwykle to robią. Pytanie brzmi, dlaczego to nie powinno działać tutaj ?
mat.

1
Nadal nie działa w Xcode 7.1, ale komunikat o błędzie jest teraz „używanie„ P ”jako konkretnego typu zgodnego z protokołem„ P ”nie jest obsługiwane” .
Martin R

1
@MartinR To lepszy komunikat o błędzie. Ale nadal wydaje mi się, że jest to dziura w języku.
mat

Pewnie! Nawet z protocol P : Q { }, P nie jest zgodne z Q.
Martin R

Odpowiedzi:


66

EDYCJA: Osiemnaście kolejnych miesięcy pracy w / Swift, kolejna ważna wersja (która zapewnia nową diagnostykę) i komentarz @AyBayBay sprawia, że ​​chcę przepisać tę odpowiedź. Nowa diagnostyka to:

„Używanie 'P' jako konkretnego typu zgodnego z protokołem 'P' nie jest obsługiwane.”

To właściwie sprawia, że ​​cała sprawa jest o wiele jaśniejsza. To rozszerzenie:

extension Array where Element : P {

nie ma zastosowania, gdy Element == Pponieważ Pnie jest uważane za konkretną zgodność P. (Poniższe rozwiązanie „włóż to do pudełka” jest nadal najbardziej ogólnym rozwiązaniem).


Stara odpowiedź:

To kolejny przypadek metatypów. Swift naprawdę chce, żebyś doszedł do konkretnego typu większości nietrywialnych rzeczy. [P]nie jest konkretnym typem (nie można przydzielić bloku pamięci o znanym rozmiarze P). (Nie sądzę, żeby to była prawda; absolutnie możesz stworzyć coś o dużym rozmiarze, Pponieważ jest to zrobione pośrednio ). Nie sądzę, aby istniały jakiekolwiek dowody na to, że jest to przypadek „nie powinno” działać. Wygląda to bardzo podobnie do jednego z ich przypadków „jeszcze nie działa”. (Niestety jest prawie niemożliwe, aby Apple potwierdziło różnicę między tymi przypadkami). Fakt, że Array<P>może to być typ zmienny (gdzieArraynie może) wskazuje, że wykonali już pewną pracę w tym kierunku, ale metatypy Swift mają wiele ostrych krawędzi i niezaimplementowanych przypadków. Nie sądzę, żebyś uzyskał lepszą odpowiedź „dlaczego” niż ta. „Ponieważ kompilator na to nie pozwala”. (Wiem, że to niezadowalające. Całe moje życie w Szybkim…)

Prawie zawsze rozwiązaniem jest włożenie rzeczy do pudełka. Budujemy gumkę do czcionek.

protocol P { }
struct S: P { }

struct AnyPArray {
    var array: [P]
    init(_ array:[P]) { self.array = array }
}

extension AnyPArray {
    func test<T>() -> [T] {
        return []
    }
}

let arr = AnyPArray([S()])
let result: [S] = arr.test()

Kiedy Swift pozwoli ci to zrobić bezpośrednio (czego ostatecznie się spodziewam), prawdopodobnie będzie to po prostu tworzenie tego pola automatycznie. Rekursywne wyliczenia miały dokładnie taką historię. Trzeba było je zapakować i było to niesamowicie denerwujące i ograniczające, a potem w końcu kompilator dodał, indirectaby zrobić to samo bardziej automatycznie.


Wiele przydatnych informacji w tej odpowiedzi, ale rzeczywiste rozwiązanie w odpowiedzi Tomohiro jest lepsze niż przedstawione tutaj rozwiązanie bokserskie.
jsadler

@jsadler Pytanie nie dotyczyło tego, jak obejść ograniczenie, ale dlaczego ono istnieje. Rzeczywiście, jeśli chodzi o wyjaśnienia, obejście Tomohiro rodzi więcej pytań niż odpowiedzi. Jeśli użyjemy ==w moim przykładzie Array, otrzymamy błąd, wymaganie tego samego typu sprawia, że ​​parametr ogólny „Element” nie jest generyczny. ”Dlaczego użycie Tomohiro nie ==generuje tego samego błędu?
Matt

@Rob Napier Wciąż jestem zakłopotany twoją odpowiedzią. W jaki sposób Swift widzi większą konkretność w Twoim rozwiązaniu w porównaniu z oryginałem? Państwo wydawało się, że po prostu zawinięte rzeczy w struct ... Idk może jestem stara się zrozumieć system typu Swift, ale to wszystko wydaje się magii voodoo
AyBayBay

@AyBayBay Zaktualizowana odpowiedź.
Rob Napier

Dziękuję bardzo @RobNapier. Zawsze jestem zdumiony szybkością twoich odpowiedzi i szczerze mówiąc, jak znajdujesz czas, aby pomagać ludziom tak bardzo jak ty. Niemniej jednak Twoje nowe edycje zdecydowanie nadają mu perspektywę. Jeszcze jedna rzecz, na którą chciałbym zwrócić uwagę, pomogło mi również zrozumienie usuwania typów. W szczególności ten artykuł spisał się fantastycznie: krakendev.io/blog/generic-protocols-and-their-shortcomings TBH Nie wiem, co sądzę o niektórych z tych rzeczy. Wydaje się, że jesteśmy rozliczania otworów w języku ale Idk jak Apple będzie budować niektóre to w.
AyBayBay

109

Dlaczego protokoły nie dostosowują się do siebie?

Pozwalanie protokołom na dostosowywanie się do siebie w ogólnym przypadku jest nieuzasadnione. Problem tkwi w statycznych wymaganiach protokołu.

Obejmują one:

  • static metody i właściwości
  • Inicjatory
  • Powiązane typy (chociaż obecnie uniemożliwiają one użycie protokołu jako rzeczywistego typu)

Możemy uzyskać dostęp do tych wymagań w ogólnym symbolu zastępczym, Tgdzie T : P- jednak nie możemy uzyskać do nich dostępu w samym typie protokołu, ponieważ nie ma konkretnego zgodnego typu, do którego można by przekazać. Dlatego nie możemy pozwolić T, aby być P.

Zastanów się, co by się stało w poniższym przykładzie, gdybyśmy pozwolili, aby Arrayrozszerzenie miało zastosowanie do [P]:

protocol P {
  init()
}

struct S  : P {}
struct S1 : P {}

extension Array where Element : P {
  mutating func appendNew() {
    // If Element is P, we cannot possibly construct a new instance of it, as you cannot
    // construct an instance of a protocol.
    append(Element())
  }
}

var arr: [P] = [S(), S1()]

// error: Using 'P' as a concrete type conforming to protocol 'P' is not supported
arr.appendNew()

Nie możemy wywołać appendNew()a [P], ponieważ P(the Element) nie jest typem konkretnym i dlatego nie można go utworzyć. To musi być wywołana na tablicy z elementami betonowymi wpisany, gdzie zgodnym typ do P.

Podobnie jest z metodą statyczną i wymaganiami dotyczącymi właściwości:

protocol P {
  static func foo()
  static var bar: Int { get }
}

struct SomeGeneric<T : P> {

  func baz() {
    // If T is P, what's the value of bar? There isn't one – because there's no
    // implementation of bar's getter defined on P itself.
    print(T.bar)

    T.foo() // If T is P, what method are we calling here?
  }
}

// error: Using 'P' as a concrete type conforming to protocol 'P' is not supported
SomeGeneric<P>().baz()

Nie możemy rozmawiać w kategoriach SomeGeneric<P>. Potrzebujemy konkretnych implementacji statycznych wymagań protokołu (zwróć uwagę, że w powyższym przykładzie nie ma implementacji foo()ani barzdefiniowanych). Chociaż możemy zdefiniować implementacje tych wymagań w Prozszerzeniu, są one zdefiniowane tylko dla konkretnych typów, z którymi są zgodne P- nadal nie można ich wywołać Psamodzielnie.

Z tego powodu Swift po prostu całkowicie zabrania nam używania protokołu jako typu, który jest zgodny ze sobą - ponieważ jeśli ten protokół ma statyczne wymagania, tak nie jest.

Wymogi protokół instancji nie są problematyczne, jak należy zadzwonić do nich na przykład, że rzeczywisty zgodny z protokołem (a zatem musi wdrożyły wymogi). Więc podczas wywoływania wymagania w wystąpieniu wpisanym jako P, możemy po prostu przekazać to wywołanie do implementacji tego wymagania bazowego konkretnego typu.

Jednak zrobienie specjalnych wyjątków od reguły w tym przypadku może prowadzić do zaskakujących niespójności w sposobie traktowania protokołów przez kod ogólny. Mimo to sytuacja nie różni się zbytnio od associatedtypewymagań, które (obecnie) uniemożliwiają używanie protokołu jako typu. Ograniczenie uniemożliwiające używanie protokołu jako typu zgodnego ze sobą, gdy ma wymagania statyczne, może być opcją dla przyszłej wersji języka

Edycja: I jak zbadano poniżej, wygląda to na to, do czego dąży zespół Swift.


@objc protokoły

W rzeczywistości dokładnie tak traktuje @objcprotokoły w języku . Kiedy nie mają statycznych wymagań, dostosowują się do siebie.

Następujące kompilacje dobrze się komponują:

import Foundation

@objc protocol P {
  func foo()
}

class C : P {
  func foo() {
    print("C's foo called!")
  }
}

func baz<T : P>(_ t: T) {
  t.foo()
}

let c: P = C()
baz(c)

bazwymaga, co Tjest zgodne z P; ale możemy podstawić Pza Tponieważ Pnie ma wymagania statyczne. Jeśli dodamy wymaganie statyczne do P, przykład już się nie kompiluje:

import Foundation

@objc protocol P {
  static func bar()
  func foo()
}

class C : P {

  static func bar() {
    print("C's bar called")
  }

  func foo() {
    print("C's foo called!")
  }
}

func baz<T : P>(_ t: T) {
  t.foo()
}

let c: P = C()
baz(c) // error: Cannot invoke 'baz' with an argument list of type '(P)'

Zatem jednym obejściem tego problemu jest utworzenie protokołu @objc. To prawda, nie jest to idealne obejście w wielu przypadkach, ponieważ wymusza to, aby twoje zgodne typy były klasami, a także wymagały środowiska uruchomieniowego Obj-C, dlatego nie czyni go opłacalnym na platformach innych niż Apple, takich jak Linux.

Ale podejrzewam, że to ograniczenie jest (jednym z) głównych powodów, dla których język już implementuje „protokół bez statycznych wymagań dostosowuje się do siebie” dla @objcprotokołów. Kod generyczny napisany wokół nich może zostać znacznie uproszczony przez kompilator.

Czemu? Ponieważ @objcwartości typu protokołu są w rzeczywistości tylko odwołaniami do klas, których wymagania są wysyłane za pomocą objc_msgSend. Z drugiej strony, wartości nietypowe dla @objcprotokołu są bardziej skomplikowane, ponieważ zawierają zarówno tabele wartości, jak i tablice świadków, aby zarówno zarządzać pamięcią ich opakowanych wartości (potencjalnie pośrednio przechowywanych), jak i określić, jakie implementacje wywołać dla różnych wymagania, odpowiednio.

Ze względu na tę uproszczoną reprezentację @objcprotokołów, wartość takiego typu protokołu Pmoże mieć tę samą reprezentację pamięci, co „wartość ogólna” typu jakiegoś ogólnego symbolu zastępczego T : P, prawdopodobnie ułatwiając zespołowi Swift umożliwienie samozgodności. To samo nie jest prawdą w przypadku @objcprotokołów innych niż protokoły, jednak takie wartości ogólne nie zawierają obecnie tabel wartości ani protokołów świadków.

Jednak ta funkcja jest zamierzona i miejmy nadzieję, że zostanie @objcwdrożona do innych niż protokoły, co potwierdził członek zespołu Swift Slava Pestov w komentarzach SR-55 w odpowiedzi na twoje zapytanie dotyczące tego (poproszone przez to pytanie ):

Matt Neuburg dodał komentarz - 7 września 2017 13:33

To się kompiluje:

@objc protocol P {}
class C: P {}

func process<T: P>(item: T) -> T { return item }
func f(image: P) { let processed: P = process(item:image) }

Dodanie @objcpowoduje kompilację; usunięcie go sprawi, że nie będzie ponownie kompilowany. Niektórzy z nas, którzy odwiedzili Stack Overflow, uważają to za zaskakujące i chcieliby wiedzieć, czy jest to celowe, czy wadliwe.

Slava Pestov dodał komentarz - 7 września 2017 13:53

To celowe - zniesienie tego ograniczenia jest tym, o co chodzi w tym błędzie. Jak powiedziałem, jest to trudne i nie mamy jeszcze żadnych konkretnych planów.

Miejmy więc nadzieję, że pewnego dnia język będzie obsługiwał również @objcprotokoły inne niż protokoły.

Ale jakie są obecne rozwiązania dla innych niż @objcprotokoły?


Implementowanie rozszerzeń z ograniczeniami protokołu

W Swift 3.1, jeśli chcesz mieć rozszerzenie z ograniczeniem, że dany ogólny symbol zastępczy lub powiązany typ musi być danym typem protokołu (nie tylko konkretnym typem zgodnym z tym protokołem) - możesz po prostu zdefiniować to za pomocą ==ograniczenia.

Na przykład, możemy napisać rozszerzenie tablicy jako:

extension Array where Element == P {
  func test<T>() -> [T] {
    return []
  }
}

let arr: [P] = [S()]
let result: [S] = arr.test()

Oczywiście to teraz uniemożliwia nam wywoływanie go w tablicy z konkretnymi elementami typu, z którymi są zgodne P. Moglibyśmy rozwiązać ten problem, definiując po prostu dodatkowe rozszerzenie określające kiedy Element : P, i po prostu przekazując do == Prozszerzenia:

extension Array where Element : P {
  func test<T>() -> [T] {
    return (self as [P]).test()
  }
}

let arr = [S()]
let result: [S] = arr.test()

Warto jednak zauważyć, że spowoduje to konwersję tablicy O (n) do a [P], ponieważ każdy element będzie musiał być opakowany w egzystencjalny kontener. Jeśli problemem jest wydajność, możesz po prostu rozwiązać ten problem, ponownie wdrażając metodę rozszerzenia. Nie jest to w pełni satysfakcjonujące rozwiązanie - miejmy nadzieję, że przyszła wersja języka będzie zawierać sposób wyrażenia ograniczenia „typ protokołu lub zgodność z typem protokołu”.

Przed wersją Swift 3.1 najbardziej ogólnym sposobem osiągnięcia tego celu, jak pokazuje Rob w swojej odpowiedzi , jest po prostu zbudowanie typu opakowania dla a [P], na którym można następnie zdefiniować metody rozszerzające.


Przekazywanie wystąpienia typu protokołu do ograniczonego ogólnego symbolu zastępczego

Rozważmy następującą (wymyśloną, ale nie rzadką) sytuację:

protocol P {
  var bar: Int { get set }
  func foo(str: String)
}

struct S : P {
  var bar: Int
  func foo(str: String) {/* ... */}
}

func takesConcreteP<T : P>(_ t: T) {/* ... */}

let p: P = S(bar: 5)

// error: Cannot invoke 'takesConcreteP' with an argument list of type '(P)'
takesConcreteP(p)

Nie możemy przejść pdo takesConcreteP(_:), ponieważ obecnie nie możemy zastąpić Pogólnego symbolu zastępczego T : P. Przyjrzyjmy się kilku sposobom rozwiązania tego problemu.

1. Otwieranie egzystencji

Zamiast próby zastąpienia Pprzez T : Pco, jeśli mogliśmy kopać bazowego typu betonowego, że Pstosunek był wpisany do owijania i substytut, że zamiast tego? Niestety wymaga to funkcji języka o nazwie otwieranie egzystencjalnych , która obecnie nie jest bezpośrednio dostępna dla użytkowników.

Jednak Swift ma domyślnie otwarte existentials (wartości protokołu wpisany) przy dostępie członków na nich (czyli wykopuje się rodzaj wykonania i czyni ją dostępną w formie ogólnej zastępczy). Możemy wykorzystać ten fakt w rozszerzeniu protokołu na P:

extension P {
  func callTakesConcreteP/*<Self : P>*/(/*self: Self*/) {
    takesConcreteP(self)
  }
}

Zwróć uwagę na niejawny ogólny Selfsymbol zastępczy, który przyjmuje metoda rozszerzenia, który jest używany do wpisywania niejawnego selfparametru - dzieje się to za kulisami ze wszystkimi członkami rozszerzenia protokołu. Podczas wywoływania takiej metody na wartości wpisanej w protokole P, Swift wykopuje podstawowy typ konkretny i używa go do spełnienia Selfogólnego symbolu zastępczego. To dlatego, że jesteśmy w stanie wywołać takesConcreteP(_:)z self- jesteśmy zaspokojenia Tz Self.

Oznacza to, że możemy teraz powiedzieć:

p.callTakesConcreteP()

I takesConcreteP(_:)jest wywoływany, gdy jego ogólny symbol zastępczy Tjest spełniony przez podstawowy konkretny typ (w tym przypadku S). Zwróć uwagę, że to nie jest „protokoły zgodne ze sobą”, ponieważ zastępujemy konkretny typ zamiast P- spróbuj dodać statyczne wymaganie do protokołu i zobaczyć, co się stanie, gdy wywołasz go od wewnątrz takesConcreteP(_:).

Jeśli Swift nadal nie zezwala protokołom na dostosowywanie się do samych siebie, następną najlepszą alternatywą byłoby niejawne otwarcie egzystencjalnych elementów podczas próby przekazania ich jako argumentów do parametrów typu ogólnego - skutecznie robiąc dokładnie to, co zrobiła nasza trampolina rozszerzająca protokół, tylko bez szablonu.

Należy jednak pamiętać, że otwarcie egzystencjalnych nie jest ogólnym rozwiązaniem problemu protokołów niezgodnych ze sobą. Nie zajmuje się heterogenicznymi kolekcjami wartości typu protokołu, które mogą mieć różne podstawowe typy konkretnych. Weźmy na przykład pod uwagę:

struct Q : P {
  var bar: Int
  func foo(str: String) {}
}

// The placeholder `T` must be satisfied by a single type
func takesConcreteArrayOfP<T : P>(_ t: [T]) {}

// ...but an array of `P` could have elements of different underlying concrete types.
let array: [P] = [S(bar: 1), Q(bar: 2)]

// So there's no sensible concrete type we can substitute for `T`.
takesConcreteArrayOfP(array) 

Z tych samych powodów funkcja z wieloma Tparametrami również byłaby problematyczna, ponieważ parametry muszą przyjmować argumenty tego samego typu - jednak jeśli mamy dwie Pwartości, nie ma możliwości zagwarantowania w czasie kompilacji, że oba mają ten sam podstawowy konkret rodzaj.

Aby rozwiązać ten problem, możemy użyć gumki typu.

2. Zbuduj gumkę typu

Jak mówi Rob , gumka typu jest najbardziej ogólnym rozwiązaniem problemu niezgodności protokołów. Pozwalają nam zawinąć wystąpienie z typem protokołu w konkretny typ, który jest zgodny z tym protokołem, przekazując wymagania wystąpienia do podstawowej instancji.

Stwórzmy więc pole wymazywania typu, które przekazuje Pwymagania instancji do bazowej arbitralnej instancji, która jest zgodna z P:

struct AnyP : P {

  private var base: P

  init(_ base: P) {
    self.base = base
  }

  var bar: Int {
    get { return base.bar }
    set { base.bar = newValue }
  }

  func foo(str: String) { base.foo(str: str) }
}

Teraz możemy po prostu rozmawiać w kategoriach AnyPzamiast P:

let p = AnyP(S(bar: 5))
takesConcreteP(p)

// example from #1...
let array = [AnyP(S(bar: 1)), AnyP(Q(bar: 2))]
takesConcreteArrayOfP(array)

Teraz zastanów się przez chwilę, dlaczego musieliśmy zbudować to pudełko. Jak omówiliśmy wcześniej, Swift potrzebuje konkretnego typu w przypadkach, w których protokół ma wymagania statyczne. Zastanów się, czy Pmiałbyś statyczne wymaganie - musielibyśmy je zaimplementować w AnyP. Ale co powinno być zaimplementowane jako? Mamy do czynienia z dowolnymi instancjami, które są Ptutaj zgodne - nie wiemy, w jaki sposób ich podstawowe typy konkretne implementują wymagania statyczne, dlatego nie możemy tego sensownie wyrazić w AnyP.

Dlatego rozwiązanie w tym przypadku jest naprawdę przydatne tylko w przypadku wymagań protokołu instancji . W ogólnym przypadku nadal nie możemy traktować Pjako zgodnego typu konkretnego P.


2
Może po prostu jestem gęsty, ale nie rozumiem, dlaczego statyczny przypadek jest wyjątkowy. My (kompilator) wiemy tyle samo lub tak mało o statycznej właściwości protokołu Prototol w czasie kompilacji, jak wiemy o właściwości instancji protokołu, a mianowicie o tym, że implementujący ją zaimplementuje. Więc jaka jest różnica?
mat.

1
@matt Wystąpienie z typem protokołu (tj. instancja typu konkretnego zawinięta w egzystencjalny P) jest w porządku, ponieważ możemy po prostu przekazywać wywołania wymagań instancji do instancji bazowej. Jednak dla samego typu protokołu (tj. A P.Protocol, dosłownie tylko typu opisującego protokół) - nie ma adoptera, dlatego nie ma na czym wywoływać statycznych wymagań, dlatego w powyższym przykładzie nie możemy mieć SomeGeneric<P>(jest to inny dla P.Type(egzystencjalnego metatypu), który opisuje konkretny mettyp czegoś, co jest zgodne z P- ale to już inna historia)
Hamish

Pytanie, które zadaję na górze tej strony, brzmi: dlaczego adopter typu protokołu jest w porządku, a sam typ protokołu nie. Rozumiem, że dla samego typu protokołu nie ma jednostki adoptującej. - Nie rozumiem, dlaczego przekierowywanie wywołań statycznych do typu adoptującego jest trudniejsze niż przekazywanie wywołań instancji do typu adoptującego. Argumentujesz, że przyczyną tej trudności jest w szczególności natura wymagań statycznych, ale nie widzę, jak wymagania statyczne są trudniejsze niż wymagania instancji.
mat.

@matt Nie chodzi o to, że wymagania statyczne są „trudniejsze” niż wymagania dotyczące instancji - kompilator radzi sobie dobrze zarówno z egzystencjalnymi instancjami (np. instancja wpisana jako P), jak i egzystencjalnymi metatypami (tj. P.Typemetatypami). Problem w tym, że w przypadku leków generycznych - tak naprawdę nie porównujemy podobieństw. Kiedy Tjest P, nie ma typu underyling betonu (meta), do którego można przesłać wymagania statyczne ( Tjest a P.Protocol, a nie a P.Type) ....
Hamish

1
Naprawdę nie obchodzi mnie solidność itp., Po prostu chcę pisać aplikacje i jeśli wydaje mi się, że powinno działać, po prostu powinno. Język powinien być tylko narzędziem, a nie samym produktem. Jeśli są przypadki, w których to naprawdę by nie zadziałało, dobrze nie zezwalaj na to w takich przypadkach, ale pozwól wszystkim innym używać przypadków, w których działa, i pozwól im pisać aplikacje.
Jonathan.

17

Jeśli rozszerzasz CollectionTypeprotokół zamiast Arrayi ograniczasz przez protokół jako konkretny typ, możesz przepisać poprzedni kod w następujący sposób.

protocol P { }
struct S: P { }

let arr:[P] = [ S() ]

extension CollectionType where Generator.Element == P {
    func test<T>() -> [T] {
        return []
    }
}

let result : [S] = arr.test()

Nie sądzę Collection vs Array jest tutaj istotne, ważna zmiana używa == Pvs : P. Z == oryginalny przykład też działa. Potencjalnym problemem (w zależności od kontekstu) z == jest to, że wyklucza podprotokoły : jeśli utworzę a protocol SubP: P, a następnie zdefiniuję arrjako, [SubP]to arr.test()już nie będzie działać (błąd: SubP i P muszą być równoważne).
imre
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.