2015-08-01 16 views
6

Come esercizio di apprendimento sto riscrivendo il mio validation library in Swift.Come si aggiungono tipi diversi conformi a un protocollo con un tipo associato a una raccolta?

Ho un protocollo ValidationRule che definisce quali regole individuali dovrebbe essere simile:

protocol ValidationRule { 
    typealias InputType 
    func validateInput(input: InputType) -> Bool 
    //... 
} 

Il tipo associato InputType definisce il tipo di ingresso da convalidare (per esempio String). Può essere esplicito o generico.

Qui ci sono due regole:

struct ValidationRuleLength: ValidationRule { 
    typealias InputType = String 
    //... 
} 

struct ValidationRuleCondition<T>: ValidationRule { 
    typealias InputType = T 
    // ... 
} 

Altrove, ho una funzione che convalida un ingresso con una collezione di ValidationRule s:

static func validate<R: ValidationRule>(input i: R.InputType, rules rs: [R]) -> ValidationResult { 
    let errors = rs.filter { !$0.validateInput(i) }.map { $0.failureMessage } 
    return errors.isEmpty ? .Valid : .Invalid(errors) 
} 

ho pensato che questo stava andando al lavoro, ma il compilatore non è d'accordo.

Nell'esempio che segue, anche se l'input è una stringa, rule1 s' InputType è una stringa, e rule2 s InputType è una stringa ...

func testThatItCanEvaluateMultipleRules() { 

    let rule1 = ValidationRuleCondition<String>(failureMessage: "message1") { $0.characters.count > 0 } 
    let rule2 = ValidationRuleLength(min: 1, failureMessage: "message2") 

    let invalid = Validator.validate(input: "", rules: [rule1, rule2]) 
    XCTAssertEqual(invalid, .Invalid(["message1", "message2"])) 

} 

... sto diventando estremamente messaggio di errore utile:

_ non è convertibile in ValidationRuleLength

che è criptico ma suggerisce che i tipi dovrebbero essere esattamente uguali?

Quindi la mia domanda è ... come posso aggiungere tipi diversi che sono tutti conformi a un protocollo con un tipo associato in una raccolta?

Non sei sicuro di come ottenere ciò che sto tentando o se è addirittura possibile?

EDIT

Ecco è senza contesto:

protocol Foo { 
    typealias FooType 
    func doSomething(thing: FooType) 
} 

class Bar<T>: Foo { 
    typealias FooType = T 
    func doSomething(thing: T) { 
     print(thing) 
    } 
} 

class Baz: Foo { 
    typealias FooType = String 
    func doSomething(thing: String) { 
     print(thing) 
    } 
} 

func doSomethingWithFoos<F: Foo>(thing: [F]) { 
    print(thing) 
} 

let bar = Bar<String>() 
let baz = Baz() 
let foos: [Foo] = [bar, baz] 

doSomethingWithFoos(foos) 

Qui abbiamo:

protocollo Foo può essere utilizzato solo come un vincolo generico, perché ha Sé o requisiti di tipo associati.

Lo capisco. Quello che devo dire è qualcosa di simile:

doSomethingWithFoos<F: Foo where F.FooType == F.FooType>(thing: [F]) { 

} 
+0

Quale versione di Swift stai usando? – matt

+0

Sto usando l'ultimissimo Swift 2 –

+0

Che cos'è un ValidationResult? Si prega di fornire il codice _enough in modo che io possa riprodurre_. – matt

risposta

10

I protocolli con alias di tipo non possono essere utilizzati in questo modo. Swift non ha modo di parlare direttamente di meta-tipi come ValidationRule o Array. Puoi gestire solo istanze come ValidationRule where... o Array<String>. Con tipealias, non c'è modo di arrivare direttamente. Quindi dobbiamo arrivare indirettamente con la cancellazione del tipo.

Swift ha diversi tipi di cancellatori di testo. AnySequence, AnyGenerator, AnyForwardIndex, ecc. Queste sono versioni generiche dei protocolli. Possiamo costruire la nostra AnyValidationRule:

struct AnyValidationRule<InputType>: ValidationRule { 
    private let validator: (InputType) -> Bool 
    init<Base: ValidationRule where Base.InputType == InputType>(_ base: Base) { 
     validator = base.validate 
    } 
    func validate(input: InputType) -> Bool { return validator(input) } 
} 

La magia profonda qui è validator. È possibile che ci sia un altro modo per cancellare i caratteri senza una chiusura, ma è il modo migliore che conosca. (Ho anche odio il fatto che Swift non può gestire validate essere una proprietà di chiusura. In Swift, getter di proprietà non sono metodi adeguati. Quindi è necessario lo strato di indirezione extra di validator.)

Con questo in luogo, si può fare i tipi di array desiderati:

let len = ValidationRuleLength() 
len.validate("stuff") 

let cond = ValidationRuleCondition<String>() 
cond.validate("otherstuff") 

let rules = [AnyValidationRule(len), AnyValidationRule(cond)] 
let passed = rules.reduce(true) { $0 && $1.validate("combined") } 

Si noti che la cancellazione del tipo non elimina la sicurezza del tipo. Semplicemente "cancella" uno strato di dettagli di implementazione. AnyValidationRule<String> è ancora diverso da AnyValidationRule<Int>, quindi questo non riuscirà:

let len = ValidationRuleLength() 
let condInt = ValidationRuleCondition<Int>() 
let badRules = [AnyValidationRule(len), AnyValidationRule(condInt)] 
// error: type of expression is ambiguous without more context 
+0

Questa è un'ottima risposta, grazie. Non penso di aver mai visto nulla di simile prima. Dove l'hai imparato? Interessato a leggere un po 'di più. –

+3

La mia fonte preferita è il file di intestazione Swift stesso. C'è una tonnellata di documentazione in là, compresi alcuni "perché" se la leggi attentamente. E passo molto tempo a cercare di costruire strane strutture dati e lamentarmi su Twitter quando non funzionano e avendo @jckarter correggermi. http://airspeedvelocity.net è anche un'ottima fonte per gli argomenti che mi interessano. –

+0

Come mai hai bisogno della chiusura e non puoi semplicemente memorizzare "base" e chiamare la convalida sulla base memorizzata quando vuoi chiamare la convalida? – JPC

Problemi correlati