Protokoll passt sich nicht an?

125

Warum wird dieser Swift-Code nicht kompiliert?

protocol P { }
struct S: P { }

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

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

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

Der Compiler sagt: "Typ Pentspricht nicht dem Protokoll P" (oder in späteren Versionen von Swift "Die Verwendung von 'P' als konkreten Typ, der dem Protokoll 'P' entspricht, wird nicht unterstützt.").

Warum nicht? Das fühlt sich irgendwie wie ein Loch in der Sprache an. Mir ist klar, dass das Problem darin besteht, das Array arrals Array eines Protokolltyps zu deklarieren. Aber ist das eine unvernünftige Sache? Ich dachte, Protokolle wären genau da, um Strukturen mit so etwas wie einer Typhierarchie zu versorgen?

matt
quelle
1
Wenn Sie die Typanmerkung in der let arrZeile entfernen , leitet der Compiler den Typ an ab [S]und der Code wird kompiliert. Es sieht so aus, als ob ein Protokolltyp nicht auf die gleiche Weise wie eine Klasse-Super-Klassen-Beziehung verwendet werden kann.
Vadian
1
@vadian Richtig, darauf habe ich mich in meiner Frage bezogen, als ich sagte "Ich erkenne, dass das Problem darin besteht, das Array arr als Array eines Protokolltyps zu deklarieren". Aber, wie ich gehe in meiner Frage zu sagen, ist der ganze Sinn der Protokolle in der Regel , dass sie können als eine Klasse in gleicher Weise verwendet werden - Super Beziehung! Sie sollen der Welt der Strukturen eine Art hierarchische Struktur verleihen. Und das tun sie normalerweise. Die Frage ist, warum sollte das hier nicht funktionieren ?
Matt
1
Funktioniert in Xcode 7.1 immer noch nicht, aber die Fehlermeldung lautet jetzt "Die Verwendung von 'P' als konkreter Typ gemäß Protokoll 'P' wird nicht unterstützt" .
Martin R
1
@ MartinR Es ist eine bessere Fehlermeldung. Aber es fühlt sich für mich immer noch wie ein Loch in der Sprache an.
Matt
Sicher! Auch mit protocol P : Q { }, P entspricht nicht Q.
Martin R

Antworten:

66

BEARBEITEN: Achtzehn weitere Monate Arbeit mit Swift, einer weiteren Hauptversion (die eine neue Diagnose bietet) und ein Kommentar von @AyBayBay veranlassen mich, diese Antwort neu zu schreiben. Die neue Diagnose lautet:

"Die Verwendung von 'P' als konkreter Typ gemäß Protokoll 'P' wird nicht unterstützt."

Das macht das Ganze tatsächlich viel klarer. Diese Erweiterung:

extension Array where Element : P {

gilt nicht, wenn Element == Pda Pnicht als konkrete Konformität von angesehen wird P. (Die unten stehende Lösung "Put it in a Box" ist immer noch die allgemeinste Lösung.)


Alte Antwort:

Es ist ein weiterer Fall von Metatypen. Swift möchte wirklich, dass Sie für die meisten nicht trivialen Dinge zu einem konkreten Typ gelangen. [P]ist kein konkreter Typ (Sie können keinen Speicherblock bekannter Größe zuweisen P). (Ich denke nicht, dass das tatsächlich wahr ist. Sie können absolut etwas von Größe erstellen, Pweil es über Indirektion erfolgt .) Ich glaube nicht, dass es Beweise dafür gibt, dass dies ein Fall ist, bei dem "nicht" funktionieren sollte. Dies sieht sehr nach einem ihrer Fälle aus, in denen "noch nicht funktioniert". (Leider ist es fast unmöglich, Apple dazu zu bringen, den Unterschied zwischen diesen Fällen zu bestätigen.) Die Tatsache, dass es Array<P>sich um einen Variablentyp handeln kann (woArraykann nicht) zeigt an, dass sie bereits einige Arbeiten in dieser Richtung ausgeführt haben, aber Swift-Metatypen haben viele scharfe Kanten und nicht implementierte Fälle. Ich glaube nicht, dass Sie eine bessere "Warum" -Antwort bekommen werden. "Weil der Compiler es nicht zulässt." (Unbefriedigend, ich weiß. Mein ganzes schnelles Leben ...)

Die Lösung besteht fast immer darin, Dinge in eine Schachtel zu packen. Wir bauen einen Radiergummi.

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()

Wenn Swift dies direkt zulässt (was ich irgendwann erwarte), wird dies wahrscheinlich nur durch automatisches Erstellen dieser Box für Sie geschehen. Rekursive Aufzählungen hatten genau diese Geschichte. Sie mussten sie boxen und es war unglaublich nervig und einschränkend, und schließlich fügte der Compiler hinzu indirect, um das Gleiche automatischer zu tun.

Rob Napier
quelle
Viele nützliche Informationen in dieser Antwort, aber die tatsächliche Lösung in Tomohiros Antwort ist besser als die hier vorgestellte Boxlösung.
Jsadler
@jsadler Die Frage war nicht, wie man die Einschränkung umgeht, sondern warum die Einschränkung existiert. In der Tat wirft Tomohiros Problemumgehung, was die Erklärung betrifft, mehr Fragen auf als sie beantwortet. Wenn wir ==in meinem Array-Beispiel verwenden, erhalten wir einen Fehler. Die Anforderung vom gleichen Typ macht den generischen Parameter 'Element' nicht generisch. "Warum ==generiert Tomohiros Verwendung nicht denselben Fehler?
Matt
@ Rob Napier Ich bin immer noch ratlos über Ihre Antwort. Wie sieht Swift mehr Konkretheit in Ihrer Lösung gegenüber dem Original? Sie schienen gerade Dinge in eine Struktur eingewickelt zu haben ... Idk, vielleicht habe ich Schwierigkeiten, das schnelle Typsystem zu verstehen, aber das alles scheint wie magisches Voodoo
AyBayBay
@ AyBayBay Antwort aktualisiert.
Rob Napier
Vielen Dank @RobNapier Ich bin immer wieder erstaunt über die Geschwindigkeit Ihrer Antworten und ehrlich gesagt, wie Sie die Zeit finden, Menschen genauso zu helfen wie Sie. Trotzdem haben Ihre neuen Änderungen es definitiv relativiert. Eine weitere Sache, auf die ich hinweisen möchte, ist, dass mir das Verständnis der Typlöschung ebenfalls geholfen hat. Insbesondere dieser Artikel hat fantastische Arbeit geleistet: krakendev.io/blog/generic-protocols-and-their-shortcomings TBH Idk, wie ich mich über einige dieser Dinge fühle. Es scheint, als würden wir Lücken in der Sprache erklären, aber Idk, wie Apple etwas davon einbauen würde.
AyBayBay
109

Warum passen sich Protokolle nicht an sich selbst an?

Es ist nicht sinnvoll, Protokolle im allgemeinen Fall an sich selbst anpassen zu lassen. Das Problem liegt in den statischen Protokollanforderungen.

Diese beinhalten:

  • static Methoden und Eigenschaften
  • Initialisierer
  • Zugehörige Typen (obwohl diese derzeit die Verwendung eines Protokolls als tatsächlichen Typ verhindern)

Wir können über einen generischen Platzhalter auf diese Anforderungen zugreifen, Twobei T : Pwir jedoch nicht über den Protokolltyp selbst darauf zugreifen können, da es keinen konkreten konformen Typ gibt, auf den weitergeleitet werden kann. Deshalb können wir nicht zulassen , dass Tsein P.

Überlegen Sie, was im folgenden Beispiel passieren würde, wenn die ArrayErweiterung anwendbar wäre auf [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()

Wir können unmöglich appendNew()auf a zurückgreifen [P], weil P(the Element) kein konkreter Typ ist und daher nicht instanziiert werden kann. Es muss in einem Array mit konkreten Elementen aufgerufen werden, wobei dieser Typ übereinstimmt P.

Ähnlich verhält es sich mit statischen Methoden- und Eigenschaftsanforderungen:

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()

Wir können nicht in Bezug auf sprechen SomeGeneric<P>. Wir benötigen konkrete Implementierungen der statischen Protokollanforderungen (beachten Sie, dass im obigen Beispiel keine Implementierungen vorhanden foo()oder bardefiniert sind). Obwohl wir Implementierungen dieser Anforderungen in einer PErweiterung definieren können, werden diese nur für die konkreten Typen definiert, die den Anforderungen entsprechen P- Sie können sie immer noch nicht selbst aufrufen P.

Aus diesem Grund verbietet Swift uns völlig, ein Protokoll als einen Typ zu verwenden, der sich selbst entspricht - denn wenn dieses Protokoll statische Anforderungen hat, ist dies nicht der Fall.

Die Anforderungen an das Instanzprotokoll sind nicht problematisch, da Sie sie auf einer tatsächlichen Instanz aufrufen müssen , die dem Protokoll entspricht (und daher die Anforderungen implementiert haben muss). Wenn Pwir also eine Anforderung für eine Instanz aufrufen, die als eingegeben wurde , können wir diesen Aufruf einfach an die Implementierung dieser Anforderung durch den zugrunde liegenden konkreten Typ weiterleiten.

In diesem Fall können jedoch spezielle Ausnahmen für die Regel zu überraschenden Inkonsistenzen bei der Behandlung von Protokollen durch generischen Code führen. Trotzdem ist die Situation den associatedtypeAnforderungen nicht allzu unähnlich - was Sie (derzeit) daran hindert, ein Protokoll als Typ zu verwenden. Eine Einschränkung, die Sie daran hindert, ein Protokoll als einen Typ zu verwenden, der sich selbst anpasst, wenn statische Anforderungen gestellt werden, könnte eine Option für eine zukünftige Version der Sprache sein

Bearbeiten: Und wie weiter unten erläutert, sieht dies so aus, wie es das Swift-Team anstrebt.


@objc Protokolle

Und genau so behandelt die Sprache @objcProtokolle. Wenn sie keine statischen Anforderungen haben, passen sie sich an sich selbst an.

Folgendes kompiliert ganz gut:

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)

bazerfordert, dass Tentspricht P; aber wir können in Ersatz Pfür Tda Pkeine statischen Anforderungen. Wenn wir eine statische Anforderung hinzufügen P, wird das Beispiel nicht mehr kompiliert:

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)'

Eine Problemumgehung für dieses Problem besteht darin, Ihr Protokoll zu erstellen @objc. Zugegeben, dies ist in vielen Fällen keine ideale Problemumgehung, da Ihre konformen Typen Klassen sein müssen und die Obj-C-Laufzeit erforderlich ist, sodass sie auf Nicht-Apple-Plattformen wie Linux nicht funktionsfähig ist.

Ich vermute jedoch, dass diese Einschränkung (einer der) Hauptgründe ist, warum die Sprache für @objcProtokolle bereits "Protokoll ohne statische Anforderungen entspricht sich selbst" implementiert . Um sie herum geschriebener generischer Code kann vom Compiler erheblich vereinfacht werden.

Warum? Weil @objcprotokolltypisierte Werte praktisch nur Klassenreferenzen sind, deren Anforderungen mithilfe von gesendet werden objc_msgSend. Auf der anderen Seite sind nicht @objcprotokolltypisierte Werte komplizierter, da sie sowohl Wert- als auch Zeugen-Tabellen enthalten, um sowohl den Speicher ihres (möglicherweise indirekt gespeicherten) umschlossenen Werts zu verwalten als auch zu bestimmen, welche Implementierungen für die verschiedenen aufgerufen werden müssen Anforderungen.

Aufgrund dieser vereinfachten Darstellung für @objcProtokolle kann ein Wert eines solchen Protokolltyps Pdieselbe Speicherdarstellung wie ein 'generischer Wert' eines generischen Platzhalters verwenden T : P, was es dem Swift-Team vermutlich erleichtert, die Selbstkonformität zuzulassen. Das Gleiche gilt jedoch nicht für Nicht- @objcProtokolle, da solche generischen Werte derzeit keine Wert- oder Protokollzeugen-Tabellen enthalten.

Diese Funktion ist jedoch beabsichtigt und wird hoffentlich auf Nicht- @objcProtokolle ausgeweitet , wie von Swift-Teammitglied Slava Pestov in den Kommentaren von SR-55 als Antwort auf Ihre Anfrage dazu (veranlasst durch diese Frage ) bestätigt wurde:

Matt Neuburg hat einen Kommentar hinzugefügt - 7. September 2017 13:33 Uhr

Dies kompiliert:

@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) }

Durch Hinzufügen @objcwird es kompiliert. Wenn Sie es entfernen, wird es nicht erneut kompiliert. Einige von uns bei Stack Overflow finden dies überraschend und möchten wissen, ob dies absichtlich oder ein fehlerhafter Edge-Case ist.

Slava Pestov hat einen Kommentar hinzugefügt - 7. September 2017 13:53 Uhr

Es ist absichtlich - das Aufheben dieser Einschränkung ist das, worum es bei diesem Fehler geht. Wie ich schon sagte, es ist schwierig und wir haben noch keine konkreten Pläne.

Hoffentlich wird die Sprache eines Tages auch Nicht- @objcProtokolle unterstützen.

Aber welche aktuellen Lösungen gibt es für Nicht- @objcProtokolle?


Implementieren von Erweiterungen mit Protokollbeschränkungen

Wenn Sie in Swift 3.1 eine Erweiterung mit der Einschränkung wünschen, dass ein bestimmter generischer Platzhalter oder zugehöriger Typ ein bestimmter Protokolltyp sein muss (nicht nur ein konkreter Typ, der diesem Protokoll entspricht), können Sie dies einfach mit einer ==Einschränkung definieren.

Zum Beispiel könnten wir Ihre Array-Erweiterung wie folgt schreiben:

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

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

Dies hindert uns jetzt natürlich daran, es in einem Array mit konkreten Typelementen aufzurufen, die den Anforderungen entsprechen P. Wir könnten dies lösen, indem wir einfach eine zusätzliche Erweiterung für wann definieren Element : Pund einfach auf die == PErweiterung weiterleiten :

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

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

Es ist jedoch anzumerken, dass dies eine O (n) -Konvertierung des Arrays in a durchführt [P], da jedes Element in einem existenziellen Container verpackt werden muss. Wenn die Leistung ein Problem darstellt, können Sie dies einfach lösen, indem Sie die Erweiterungsmethode erneut implementieren. Dies ist keine völlig zufriedenstellende Lösung - hoffentlich wird eine zukünftige Version der Sprache eine Möglichkeit enthalten, eine Einschränkung "Protokolltyp oder entspricht Protokolltyp" auszudrücken .

Vor Swift 3.1 besteht der allgemeinste Weg, dies zu erreichen, wie Rob in seiner Antwort zeigt , darin, einfach einen Wrapper-Typ für a zu erstellen [P], auf dem Sie dann Ihre Erweiterungsmethode (n) definieren können.


Übergeben einer protokolltypisierten Instanz an einen eingeschränkten generischen Platzhalter

Betrachten Sie die folgende (erfundene, aber nicht ungewöhnliche) Situation:

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)

Wir können nicht passieren pzu takesConcreteP(_:), da wir derzeit nicht ersetzen können Pfür eine generische Platzhalter T : P. Schauen wir uns einige Möglichkeiten an, wie wir dieses Problem lösen können.

1. Existentials öffnen

Anstatt zu ersetzen versuchen , Pfür T : P, was passiert , wenn wir in den darunter liegenden Betontyp graben könnten , dass der Ptypisierte Wert war Wickel- und Ersatz , dass statt? Leider erfordert dies eine Sprachfunktion namens Öffnen von Existentials , die Benutzern derzeit nicht direkt zur Verfügung steht.

Swift öffnet jedoch implizit Existentials (protokolltypisierte Werte), wenn auf Mitglieder zugegriffen wird (dh es gräbt den Laufzeit-Typ aus und macht ihn in Form eines generischen Platzhalters zugänglich). Wir können diese Tatsache in einer Protokollerweiterung ausnutzen für P:

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

Beachten Sie den impliziten generischen SelfPlatzhalter, den die Erweiterungsmethode verwendet, um den impliziten selfParameter einzugeben. Dies geschieht hinter den Kulissen mit allen Protokollerweiterungsmitgliedern. Wenn PSwift eine solche Methode für einen protokolltypisierten Wert aufruft , gräbt er den zugrunde liegenden konkreten Typ aus und verwendet diesen, um den Selfgenerischen Platzhalter zu erfüllen . Aus diesem Grund können wir sind nennen takesConcreteP(_:)mit self- wir erfüllen Tmit Self.

Das heißt, wir können jetzt sagen:

p.callTakesConcreteP()

Und takesConcreteP(_:)wird aufgerufen T, wenn sein generischer Platzhalter vom zugrunde liegenden konkreten Typ (in diesem Fall S) erfüllt wird . Beachten Sie, dass dies keine "Protokolle sind, die sich selbst entsprechen", da wir eher einen konkreten Typ ersetzen als P- versuchen Sie, dem Protokoll eine statische Anforderung hinzuzufügen und zu sehen, was passiert, wenn Sie es von innen aufrufen takesConcreteP(_:).

Wenn Swift weiterhin nicht zulässt, dass Protokolle sich selbst anpassen, besteht die nächstbeste Alternative darin, implizit Existenziale zu öffnen, wenn versucht wird, sie als Argumente an Parameter vom generischen Typ zu übergeben - und genau das zu tun, was unser Protokollerweiterungstrampolin getan hat, nur ohne das Boilerplate.

Beachten Sie jedoch, dass das Öffnen von Existentials keine allgemeine Lösung für das Problem von Protokollen ist, die nicht mit sich selbst übereinstimmen. Es werden keine heterogenen Sammlungen protokolltypisierter Werte behandelt, denen möglicherweise unterschiedliche konkrete Typen zugrunde liegen. Betrachten Sie zum Beispiel:

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) 

Aus den gleichen Gründen wäre eine Funktion mit mehreren TParametern ebenfalls problematisch, da die Parameter Argumente desselben Typs annehmen müssen. Wenn wir jedoch zwei PWerte haben, können wir zum Zeitpunkt der Kompilierung nicht garantieren, dass beide denselben konkreten Grund haben Art.

Um dieses Problem zu lösen, können wir einen Radiergummi verwenden.

2. Erstellen Sie einen Radiergummi

Wie Rob sagt , ist ein Radiergummi die allgemeinste Lösung für das Problem, dass Protokolle nicht mit sich selbst übereinstimmen. Sie ermöglichen es uns, eine protokolltypisierte Instanz in einen konkreten Typ zu verpacken, der diesem Protokoll entspricht, indem wir die Instanzanforderungen an die zugrunde liegende Instanz weiterleiten.

Erstellen wir also eine Box zum Löschen von Typen, die die Instanzanforderungen Pan eine zugrunde liegende willkürliche Instanz weiterleitet , die den folgenden Anforderungen entspricht 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) }
}

Jetzt können wir nur in Bezug auf reden AnyPstatt P:

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

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

Überlegen Sie sich jetzt einen Moment, warum wir diese Box bauen mussten. Wie bereits erwähnt, benötigt Swift einen konkreten Typ für Fälle, in denen das Protokoll statische Anforderungen stellt. Überlegen Sie, ob Peine statische Anforderung vorliegt - wir hätten diese in implementieren müssen AnyP. Aber wie hätte es umgesetzt werden sollen? Wir haben es mit willkürlichen Instanzen zu Ptun , die hier übereinstimmen - wir wissen nicht, wie ihre zugrunde liegenden konkreten Typen die statischen Anforderungen implementieren, daher können wir dies nicht sinnvoll ausdrücken AnyP.

Daher ist die Lösung in diesem Fall nur dann wirklich nützlich im Fall von Beispiel Protokollanforderungen. Im allgemeinen Fall können wir immer noch nicht Pals konkreten Typ behandeln, der dem entspricht P.

Hamish
quelle
2
Vielleicht bin ich nur dicht, aber ich verstehe nicht, warum der statische Fall etwas Besonderes ist. Wir (der Compiler) wissen zur Kompilierungszeit genauso viel oder so wenig über die statische Eigenschaft eines Prototols wie über die Instanzeigenschaft eines Protokolls, nämlich dass der Anwender sie implementieren wird. Was ist der Unterschied?
Matt
1
@matt Eine protokolltypisierte Instanz (dh eine in existential umhüllte konkret typisierte Instanz P) ist in Ordnung, da wir nur Aufrufe an die Instanzanforderungen an die zugrunde liegende Instanz weiterleiten können. Für einen Protokolltyp selbst (dh einen P.Protocol, buchstäblich nur den Typ, der ein Protokoll beschreibt) gibt es jedoch keinen Anwender, daher gibt es nichts, worauf die statischen Anforderungen angewendet werden können, weshalb wir im obigen Beispiel nicht haben können SomeGeneric<P>(es ist andere für eine P.Type(existentielle Metatyp), die eine konkrete Metatyp von etwas beschreibt , dass Konform P- aber das ist eine andere Geschichte)
Hamish
Die Frage, die ich oben auf dieser Seite stelle, ist, warum ein Protokolltyp-Anwender in Ordnung ist und ein Protokolltyp selbst nicht. Ich verstehe, dass es für einen Protokolltyp selbst keinen Adopter gibt. - Was ich nicht verstehe, ist, warum es schwieriger ist, statische Aufrufe an den übernehmenden Typ weiterzuleiten, als Instanzaufrufe an den übernehmenden Typ weiterzuleiten. Sie argumentieren, dass der Grund für die Schwierigkeit hier insbesondere in der Art der statischen Anforderungen liegt, aber ich sehe nicht, wie statische Anforderungen schwieriger sind als Instanzanforderungen.
Matt
@matt Es ist nicht so, dass statische Anforderungen "schwieriger" sind als Instanzanforderungen - der Compiler kann sowohl über Existentials für Instanzen (dh als typisierte Instanz P) als auch über existenzielle Metatypen (dh P.TypeMetatypen) gut umgehen. Das Problem ist, dass wir für Generika nicht wirklich Gleiches für Gleiches vergleichen. Wann Tist P, gibt es keinen untergeordneten Beton (Meta) Typ, um statische Anforderungen an ( Tist ein P.Protocol, nicht ein P.Type) weiterzuleiten ....
Hamish
1
Ich kümmere mich wirklich nicht um Solidität usw., ich möchte nur Apps schreiben, und wenn es sich so anfühlt, als ob es funktionieren sollte, sollte es einfach funktionieren. Die Sprache sollte nur ein Werkzeug sein, kein Produkt selbst. Wenn es einige Fälle gibt, für die es wirklich nicht funktionieren würde, lassen Sie es in diesen Fällen nicht zu, aber lassen Sie alle anderen die Fälle verwenden, für die es funktioniert, und lassen Sie sie mit dem Schreiben von Apps fortfahren.
Jonathan.
17

Wenn Sie das CollectionTypeProtokoll anstelle Arrayeines konkreten Typs erweitern und durch das Protokoll einschränken, können Sie den vorherigen Code wie folgt umschreiben.

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()
Tomohiro Kumagai
quelle
Ich glaube nicht , Sammlung vs Array hier relevant ist, ist die wichtige Änderung wird mit == Pvs : P. Mit == funktioniert auch das Originalbeispiel. Und ein potenzielles Problem (je nach Kontext) mit == ist , dass es schließt Unterprotokolle: Wenn ich ein erstellen protocol SubP: Pund dann definieren , arrwie [SubP]dann arr.test()nicht mehr funktionieren werden (Fehler: SUBP und P muss gleichwertig sein).
Imre