2015-11-21 5 views
11

zum Beispiel Angenommen, wir reden über Elemente vom Typ Int (aber die Frage immer noch gilt für jede Art)Definieren Sie ein Swift-Protokoll, das eine bestimmte Art von Sequenz erfordert

ich einige Funktionen haben, die über eine Schleife benötigt Sequenz von Inten. Aber es ist mir egal, ob hinter den Kulissen diese Sequenz als ein Array oder ein Set oder irgendeine andere exotische Art von Struktur implementiert ist, die einzige Voraussetzung ist, dass wir sie überschleifen können.

Die Swift-Standardbibliothek definiert das Protokoll SequenceType als "Ein Typ, der mit einer for ... in-Schleife iteriert werden kann". Also mein Instinkt ist ein Protokoll wie folgt zu definieren:

protocol HasSequenceOfInts { 
    var seq : SequenceType<Int> { get } 
} 

Aber das funktioniert nicht. SequenceType ist kein generischer Typ, der spezialisiert werden kann, sondern ein Protokoll. Jede bestimmte SequenceType tut eine bestimmte Art von Element haben, aber es ist nur als assoziiertes Typ: SequenceType.Generator.Element

Die Frage ist also:

Wie können wir ein Protokoll definieren, die ein erfordert bestimmte Art der Sequenz?

Hier einige andere Dinge, die ich versucht habe und warum sie nicht richtig:

Ausfallen 1

protocol HasSequenceOfInts { 
    var seq : SequenceType { get } 
} 

Protokoll ‚SequenceType‘ kann nur als generische Einschränkung verwendet werden da es sich um Selbst- oder zugehörige Typanforderungen handelt

Ausfallen 2

protocol HasSequenceOfInts { 
    var seq : AnySequence<Int> { get } 
} 
class ArrayOfInts : HasSequenceOfInts { 
    var seq : [Int] = [0,1,2] 
} 

Ich dachte, das würde man arbeiten, aber wenn ich eine konkrete Umsetzung unter Verwendung eines Array versucht, bekommen wir

Typ ‚ArrayOfInts‘ entspricht nicht Protokoll ‚HasSequenceOfInts‘

Dies liegt daran, Array nicht AnySequence ist (zu meiner Überraschung ... war meine Erwartung, dass AnySequence würde jede Folge von Ints entsprechen)

Ausfallen 3

protocol HasSequenceOfInts { 
    typealias S : SequenceType 
    var seq : S { get } 
} 

Compiliert, aber es gibt keine Verpflichtung, dass die Elemente der Folge Seq Typ Int

Ausfallen 4

protocol HasSequenceOfInts { 
    var seq : SequenceType where S.Generator.Element == Int 
} 

Kann kein haben verwenden Wo gibt es dort

Also nein w Ich habe keine Ideen mehr. Ich kann einfach mein Protokoll ein Array von Int erfordern, aber dann beschränke ich die Implementierung ohne guten Grund, und das fühlt sich sehr unflexibel an.

aktualisieren Erfolg

Siehe Antwort von @ rob-napier, die Dinge sehr gut erklärt. Mein Fail 2 war ziemlich nah dran. Die Verwendung von AnySequence kann funktionieren, aber in der entsprechenden Klasse müssen Sie sicherstellen, dass Sie jede Art von Sequenz, die Sie verwenden, in AnySequence konvertieren. Zum Beispiel:

protocol HasSequenceOfInts { 
    var seq : AnySequence<Int> { get } 
} 
class ArrayOfInts : HasSequenceOfInts { 
    var _seq : [Int] = [0,1,2] 
    var seq : AnySequence<Int> { 
     get { 
      return AnySequence(self._seq) 
     } 
    } 
} 
+0

"... mein Instinkt ist es, ein Protokoll wie ... zu definieren". Mein Instinkt diktiert mir, mein eigenes Objekt (oder Protokoll) so zu definieren, dass es dem SequenceType – user3441734

+0

@ user3441734 entspricht - ich bin mir nicht sicher, wie du Suggestion hilfst. Kannst du ein Beispiel geben? –

Antwort

7

Es gibt zwei Seiten für dieses Problem:

  • eine willkürliche Folge von ints

  • annehmen

    Rückkehr oder eine beliebige Folge von ints Speichern

Im ersten Fall ist die Antwort Generika zu verwenden. Zum Beispiel:

func iterateOverInts<SeqInt: SequenceType where SeqInt.Generator.Element == Int>(xs: SeqInt) { 
    for x in xs { 
     print(x) 
    } 
} 

Im zweiten Fall benötigen Sie einen Typ-Radiergummi. Ein Type-Eraser ist ein Wrapper, der den eigentlichen Typ einer zugrunde liegenden Implementierung verbirgt und nur die Schnittstelle darstellt. Swift hat mehrere davon in stdlib, meist mit dem Präfix Any. In diesem Fall möchten Sie AnySequence.

func doubles(xs: [Int]) -> AnySequence<Int> { 
    return AnySequence(xs.lazy.map { $0 * 2 }) 
} 

Weitere Informationen über AnySequence und Typ-Radiergummis im Allgemeinen finden Sie A Little Respect for AnySequence.

Wenn Sie es in Protokollform benötigen (in der Regel Sie nicht, Sie müssen nur eine allgemeine Verwendung wie in iterateOverInts), die Art Radiergummi ist auch das Werkzeug dort:

protocol HasSequenceOfInts { 
    var seq : AnySequence<Int> { get } 
} 

Aber seq muss zurückkehren AnySequence<Int>. Es kann nicht [Int] zurückgegeben werden.

Es gibt eine weitere Schicht tiefer, die Sie nehmen können, aber manchmal schafft es mehr Ärger als es löst. Sie könnten definieren:

protocol HasSequenceOfInts { 
    typealias SeqInt : IntegerType 
    var seq: SeqInt { get } 
} 

Aber jetzt HasSequenceOfInts eine typealias mit allen Einschränkungen, was dazu gehört. SeqInt könnte jede Art von IntegerType (nicht nur Int) sein, sieht also genauso aus wie eine eingeschränkte SequenceType, und wird in der Regel einen eigenen Typ Radiergummi benötigen. Gelegentlich ist diese Technik nützlich, aber in Ihrem speziellen Fall bringt es Sie einfach zurück, wo Sie angefangen haben. Sie können SeqInt hier nicht auf Int beschränken. Es muss ein Protokoll sein (natürlich könnte man ein Protokoll erfinden und Int zum einzigen übereinstimmenden Typ machen, aber das ändert nicht viel).

BTW, in Bezug auf Typ-Radiergummis, wie Sie wahrscheinlich sehen, dass sie sehr mechanisch sind. Sie sind nur eine Box, die auf etwas anderes hinweist. Das deutet darauf hin, dass der Compiler in Zukunft diese Type-Radiergummis automatisch für uns generieren kann. Der Compiler hat im Laufe der Zeit andere Boxprobleme für uns behoben. Zum Beispiel mussten Sie früher eine Klasse Box erstellen, die Enums mit generischen Werten enthielt. Nun, das ist halbautomatisch mit indirect getan. Wir könnten uns vorstellen, dass ein ähnlicher Mechanismus hinzugefügt wird, um automatisch AnySequence zu erzeugen, wenn es vom Compiler benötigt wird. Also ich denke nicht, dass das ein tiefes ist. "Das Design von Swift erlaubt das nicht." Ich denke, es ist nur "der Swift-Compiler geht noch nicht damit um."

+1

Vielen Dank Rob, sehr hilfreich. Teil 1 der Lösung ist, wie Sie sagen, AnySequence im Protokoll. Ich erwartete, dass eine konforme Klasse ihre Ints in einem Array speichern könnte und es würde einfach funktionieren, aber wie Sie sagen, das Protokoll erfordert AnySequence , nicht [Int]. Ich habe (zu Unrecht) erwartet, dass Swift automatisch einen zum anderen werfen würde. Es ist jedoch trivial, mit einer Getter-Funktion zu überwinden. Klasse Foo: HasSequenceOfInts {var _seq: [Int] var seq: AnySequence {{AnySequence (self._seq) zurückgeben}}} –

+0

Einverstanden; Wenn das zu Ihren Bedürfnissen passt, ist das ein perfektes Design. –

+1

Auch @ rob-napier - Ausgezeichneter Artikel, den Sie verlinkt haben! (http://robnapier.net/erasure) - kommt direkt in den Kern dieses (allgemeinen) Problems und macht es kristallklar. Ich hoffe, Sie haben recht, dass der Compiler sich eines Tages darum kümmern wird. –

1

Ich glaube, Sie die Anforderung für sie nur sein Int ist und arbeiten um es mit Generika fallen müssen:

protocol HasSequence { 
    typealias S : SequenceType 
    var seq : S { get } 
} 

struct A : HasSequence { 
    var seq = [1, 2, 3] 
} 

struct B : HasSequence { 
    var seq : Set<String> = ["a", "b", "c"] 
} 

func printSum<T : HasSequence where T.S.Generator.Element == Int>(t : T) { 
    print(t.seq.reduce(0, combine: +)) 
} 

printSum(A()) 
printSum(B()) // Error: B.S.Generator.Element != Int 

In Swift aktuellen Zustand, man kann nicht genau das tun, was du willst, vielleicht in der Zukunft.

+1

Das ist hilfreich Kametrixom - Obwohl in Ihrem Beispiel Hassequence ziemlich nutzlos ist. Wenn wir die Anforderung, dass die Sequenz einen bestimmten Typ hat, fallen lassen müssen, können wir auch SequenceType direkt verwenden: func printSum (s: S) { drucken (s.reduce (0, kombinieren: +)) } } –

+0

@DanielHoward Yeah dachte ich auch, aber ich dachte, dass Sie vielleicht auch andere Anforderungen in Ihrem Protokoll wünschen – Kametrixom

+0

Es ist alles ziemlich frustrierend. Ich versuche mich an den Grundsatz "Code an eine Schnittstelle, keine Implementierung" zu halten. Also suche ich, wenn möglich, Protokolle zu verwenden, anstatt starre, konkrete Klassen zu definieren, und jedes Mal, wenn ich finde, dass Protokolle zu kurz kommen. Dies schien ein perfektes Beispiel dafür zu sein, wo ein Protokoll verwendet werden sollte: Ich brauche eine Sequenz von Inten, aber es spielt keine Rolle, wie die Sequenz implementiert wird - benutze einfach ein Protokoll, das Klassen erfordert, um eine Sequenz von Inten zu liefern wie sie wollen ... Aber ... Es ist unmöglich. Ich muss ein Array oder ein Set usw. erzwingen. –

0

es ist sehr konkretes Beispiel auf Anfrage von Daniel Howard

1) eingeben, um SequenceType Protokoll entspricht fast jede Sequenz sein könnte, auch wenn Array oder Set konform ist sowohl SequenceType Protokoll, die meisten ihrer Funktionalität kommt von Vererbung auf Collection (die SequenceType entspricht)

Daniel, in Ihrem Spielplatz

import Foundation 

public struct RandomIntGenerator: GeneratorType, SequenceType { 

    public func next() -> Int? { 
     return random() 
    } 

    public func nextValue() -> Int { 
     return next()! 
    } 
    public func generate() -> RandomIntGenerator { 
     return self 
    } 
} 

let rs = RandomIntGenerator() 

for r in rs { 
    print(r) 
} 

Wie Sie dieses einfache Beispiel versuchen zu sehen, entspricht es SequenceType Protokoll und produzieren in Endlicher Strom von Int-Zahlen. Bevor Sie versuchen, etwas zu implementieren, müssen Sie sich selbst einige Fragen beantworten

  1. kann ich einige Funktionen wiederverwenden, die 'frei' in der Standard Swift-Bibliothek verfügbar ist?
  2. Ich versuche, einige Funktionen nachzuahmen, die nicht von Swift unterstützt werden? Swift ist nicht C++, Swift ist nicht ObjectiveC ... und viele Konstruktionen, die wir vor Swift benutzt haben, gibt es in Swift nicht.
  3. Definieren Sie Ihre Frage in einer solchen Art und Weise, die wir Ihnen Anforderungen verstehen

suchen Sie dieser Suchbegriffe?

protocol P { 
    typealias Type: SequenceType 
    var value: Type { get set } 
} 
extension P { 
    func foo() { 
     for v in value { 
      dump(v) 
     } 
    } 
} 

struct S<T: CollectionType>: P { 
    typealias Type = T 
    var value: Type 
} 

var s = S(value: [Int]()) 
s.value.append(1) 
s.value.append(2) 

s.foo() 
/* 
- 1 
- 2 
*/ 

let set: Set<String> = ["alfa", "beta", "gama"] 
let s2 = S(value: set) 
s2.foo() 
/* 
- beta 
- alfa 
- gama 
*/ 

// !!!! WARNING !!! 
// this is NOT possible 
s = s2 
// error: cannot assign value of type 'S<Set<String>>' to type 'S<[Int]>' (aka 'S<Array<Int>>') 
+0

Sie haben den Punkt hier vollständig verpasst. Ich versuche nicht, eine weitere Datenstruktur zu definieren, die eine Sequenz von Inten bereitstellt. Ich versuche, ein Protokoll zu definieren, das JEDE Sequenz von Ints akzeptieren kann, so dass der Implementierer frei ist, ein Array zu verwenden, wenn das das Beste ist, oder ein Set, wenn das am besten ist, oder eine eigene benutzerdefinierte Sequenz, wenn das das Beste ist es wird immer noch dem Protokoll entsprechen, da das Protokoll nur erfordert, dass es eine Sequenz von Int ist. –

+0

... definieren Sie ein Protokoll, das jede Sequenz von INTS akzeptieren kann ... ??? Das Protokoll in Swift kann generisch über abstrakte Typ-Member statt über Parametrisierung sein. Folglich kann das Protokoll selbst nicht mehr als Typ, sondern nur als generische Einschränkung verwendet werden. – user3441734

+0

@DanielHoward bitte überprüfen Sie meine aktualisierte Antwort, ich hoffe, ich verstehe Sie – user3441734

Verwandte Themen