2015-11-21 5 views
11

Supponiamo per esempio che stiamo parlando di elementi di tipo Int (ma la questione si applica ancora a qualsiasi tipo)Definire un protocollo di Swift che richiede un tipo specifico di sequenza

ho alcune funzionalità che deve ciclare su un sequenza di Ints. Ma non mi interessa se dietro le quinte questa sequenza è implementata come una matrice, un set o qualsiasi altro tipo di struttura esotica, l'unico requisito è che possiamo ricollegarli.

La libreria standard Swift definisce il protocollo SequenceType come "Un tipo che può essere iterato con un per ... in loop". Quindi il mio istinto è di definire un protocollo come questo:

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

Ma questo non funziona. SequenceType non è un tipo generico che può essere specializzato, è un protocollo. Ogni particolare SequenceType fa hanno uno specifico tipo di elemento, ma è disponibile solo come un tipo associato: SequenceType.Generator.Element

Quindi la domanda è:

Come possiamo definire un protocollo che richiede una tipo specifico di sequenza?

Ecco alcune altre cose che ho provato e perché non sono di destra:

Fail 1

protocol HasSequenceOfInts { 
    var seq : SequenceType { get } 
} 

protocollo 'SequenceType' può essere utilizzato solo come un vincolo generico perché ha i requisiti di tipo Self o associati

Fail 2

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

ho pensato che questo avrebbe funzionato, ma quando ho provato un'implementazione concreta utilizzando una matrice otteniamo

'ArrayOfInts' Tipo non è conforme al protocollo 'HasSequenceOfInts'

Questo perché Array non è AnySequence (con mia grande sorpresa ... la mia aspettativa era che AnySequence sarebbe partita qualsiasi sequenza di INT)

Fail 3

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

compila, ma non c'è l'obbligo che gli elementi della sequenza SEQ hanno tipo Int

Fail 4

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

non può utilizzare un dove clausola

Quindi no w Sono totalmente privo di idee. Posso facilmente fare in modo che il mio protocollo richieda un array di Int, ma poi sto limitando l'implementazione senza una buona ragione, e questo mi sembra molto poco rapido.

Aggiornamento Successo

See risposta da @ rob-Napier che spiega le cose molto bene. My Fail 2 era abbastanza vicino. Usare AnySequence può funzionare, ma nella tua classe conforme devi assicurarti di convertire da AnySequence qualunque tipo di sequenza tu stia utilizzando. Per esempio:

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

"... il mio istinto è definire un protocollo come ...". il mio istinto mi impone di definire il mio oggetto (o protocollo) come conforme a SequenceType – user3441734

+0

@ user3441734 - Non sono sicuro di quale sia il tuo suggerimento. Puoi fare un esempio? –

risposta

7

Ci sono due lati a questo problema:

  • accettano una sequenza arbitraria di int

  • Tornando o memorizzare una sequenza arbitraria di int

Nel primo caso, la risposta è usare i generici. Ad esempio:

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

Nel secondo caso, è necessario un tipo-gomma. Un tipo-gomma è un wrapper che nasconde il tipo effettivo di alcune implementazioni sottostanti e presenta solo l'interfaccia. Swift ne ha diversi in stdlib, per lo più preceduti dalla parola Any. In questo caso si desidera AnySequence.

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

Per ulteriori informazioni su AnySequence e tipo-gomme in generale, vedere A Little Respect for AnySequence.

Se ne avete bisogno in forma di protocollo (di solito non è: è sufficiente utilizzare un generico come in iterateOverInts), il tipo di gomma è anche lo strumento c'è:

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

Ma seq deve tornare AnySequence<Int>. Non può restituire [Int].

C'è un altro livello più in profondità che puoi prendere, ma a volte crea più problemi di quanti ne risolva. Si potrebbe definire:

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

Ma ora HasSequenceOfInts ha un typealias con tutti i limiti che questo comporta. SeqInt potrebbe essere un qualsiasi tipo di IntegerType (non solo Int), quindi sembra proprio come un SequenceType vincolato e in genere avrà bisogno del proprio tipo di gomma. Quindi occasionalmente questa tecnica è utile, ma nel tuo caso specifico ti riporta indietro nel punto in cui hai iniziato. Non puoi vincolare SeqInt a Int qui. Deve essere un protocollo (ovviamente potresti inventare un protocollo e rendere Int l'unico tipo conforme, ma questo non cambia molto).

BTW, per quanto riguarda le gomme dei tipi, come probabilmente si può vedere sono molto meccaniche. Sono solo una scatola che inoltra a qualcos'altro. Ciò suggerisce che in futuro il compilatore sarà in grado di generare automaticamente questi tipi di gomme per noi. Il compilatore ha risolto altri problemi di boxe nel tempo. Ad esempio, era necessario creare una classe Box per contenere le enumerazioni con valori associati generici. Ora ciò avviene in modo semi-automatico con indirect. Potremmo immaginare un meccanismo simile aggiunto per creare automaticamente AnySequence quando è richiesto dal compilatore. Quindi non penso che questo sia un profondo "il disegno di Swift non lo permette". Penso che sia solo "il compilatore Swift non lo gestisce ancora".

+1

Grazie Rob, molto utile. La parte 1 della soluzione è, come dici tu, che richiede AnySequence nel protocollo. Mi aspettavo che una classe conforme potesse memorizzare i suoi Ints in una matrice e avrebbe funzionato, ma come dici tu, il protocollo richiede AnySequence , non [Int]. Mi aspettavo (a torto) che Swift si lanciasse automaticamente uno sull'altro. Tuttavia è banale da superare con una funzione getter. classe Foo: HasSequenceOfInts {var _seq: [Int] var seq: AnySequence {get {return AnySequence (self._seq)}}} –

+0

concordato; se questo si adatta alle tue esigenze, penso che sia un design perfetto. –

+1

Anche @ rob-napier - Eccellente articolo a cui sei collegato! (http://robnapier.net/erasure) - arriva dritto al cuore di questo (tipo generale di) problema e lo rende cristallino. Spero tu abbia ragione che un giorno il compilatore si prenderà cura di questo per noi. –

1

Credo che hai bisogno di abbandonare il requisito per poter essere solo Int e di lavorare intorno ad esso con i generici:

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 

Nello stato attuale di Swift, non si può fare esattamente ciò che vuoi, forse in futuro però.

+1

Questo è utile Kametrixom - Anche se nel tuo esempio, HasSequence è piuttosto inutile. Se dovessimo abbandonare il requisito che la sequenza abbia un tipo particolare, allora potremmo usare direttamente SequenceType: func printSum (s: S) { print (s.reduce (0, combine: +)) } } –

+0

@DanielHoward Sì, l'ho pensato anch'io, ma ho pensato che potreste desiderare altri requisiti nel vostro protocollo – Kametrixom

+0

È tutto piuttosto frustrante. Sto cercando di attenermi al principio del 'codice per un'interfaccia, non un'implementazione'. Quindi sto cercando di utilizzare i protocolli quando possibile, invece di definire classi rigide e concrete, e ogni volta che trovo che i protocolli non sono all'altezza. Questo mi è sembrato un perfetto esempio di dove dovrebbe essere usato un protocollo: ho bisogno di una sequenza di Ints, ma non importa come viene implementata la sequenza - quindi basta usare un protocollo che richiede l'adozione di classi per fornire una sequenza di Ints, qualsiasi modo che vogliono ... Ma ... È impossibile. Devo forzare una matrice, un set ecc. –

0

è esempio molto specifico su richiesta di Daniel Howard

1) tipo conforme al protocollo SequenceType potrebbero essere quasi tutte sequenza, anche se Array o Set sono entrambi conformi alla protocollo SequenceType, la maggior parte della loro funzionalità viene da eredità su CollectionType (conforme al SequenceType)

Daniel, provate questo semplice esempio nel vostro parco giochi

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

Come si può vedere, è conforme al protocollo SequenceType e produrre in flusso finito di numeri Int. Prima di provare a implementare qualcosa, devi rispondere a te stesso alcune domande

  1. posso riutilizzare alcune funzionalità, che è disponibile "gratuitamente" nella libreria Swift standard?
  2. sto cercando di imitare alcune funzionalità che non sono supportate da Swift? Swift non è C++, Swift non è ObjectiveC ... e molte costruzioni che usavamo prima che Swift non avesse equivalenti in Swift.
  3. Definisci la tua domanda in modo tale che possiamo capire che si requisiti

stai cercando qualcosa di simile?

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

Hai perso completamente il punto qui. Non sto cercando di definire un'altra struttura dati che fornisce una sequenza di Ints. Sto provando a definire un protocollo che possa accettare QUALSIASI sequenza di interi, in modo che l'implementatore sia libero di utilizzare un array se questo è il migliore, o un set se è il migliore, o la loro sequenza personalizzata se è quella migliore, e sarà comunque conforme al protocollo, perché il protocollo richiede solo che sia una sequenza di Int. –

+0

... definire un protocollo che può accettare QUALSIASI sequenza di ints ... ??? il protocollo in Swift può essere generico tramite membri di tipo astratto anziché parametrizzazione. di conseguenza, il protocollo stesso non può più essere usato come un tipo ma solo come un vincolo generico. – user3441734

+0

@DanielHoward si prega di controllare la mia risposta aggiornata, spero di capirti – user3441734