2015-08-01 1 views
6

Как упражнение в обучении, я переписываю свой validation library в Свифт.Как добавить в коллекцию различные типы, соответствующие протоколу с ассоциированным типом?

У меня есть ValidationRule протокол, который определяет, что отдельные правила должны выглядеть следующим образом:

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

Соответствующий тип InputType определяет тип входа быть подтверждено (например: String). Он может быть явным или общим.

Вот два правила:

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

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

В другом месте, у меня есть функция, которая проверяет вход с коллекцией ValidationRule с:

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

Я думал, что это будет работать, но компилятор не согласен.

В следующем примере, даже если входной сигнал является String, rule1 «s InputType является String, и rule2 s InputType является строка ...

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"])) 

} 

... Я получаю очень полезно сообщение об ошибке:

_ не конвертируется в ValidationRuleLength

, который является загадочным, но предполагает, что типы должны быть точно равны?

Так что мой вопрос ... как добавить различные типы, все они соответствуют протоколу с ассоциированным типом в коллекцию?

Не знаете, как добиться того, что я пытаюсь, или если это возможно?

EDIT

Вот это без контекста:

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) 

Здесь мы получаем:

Протокол Foo может быть использован только в качестве общего ограничения, поскольку он имеет Атман или связанных типов.

Я понимаю это. Что мне нужно сказать, это примерно то, что:

+0

Какую версию Swift вы используете? – matt

+0

Я использую самую последнюю версию Swift 2 –

+0

Что такое ValidationResult? Пожалуйста, предоставьте код, который я могу воспроизвести. – matt

ответ

10

Протоколы с псевдонимами типов не могут быть использованы таким образом. У Swift нет возможности напрямую говорить о мета-типах, например ValidationRule или Array. Вы можете обрабатывать только экземпляры, такие как ValidationRule where... или Array<String>. С typealiases, нет никакого способа добраться туда напрямую. Поэтому мы должны косвенно коснуться стирания стилей.

Свифт имеет несколько типов ластиков. AnySequence, AnyGenerator, AnyForwardIndex и т. Д. Это общие версии протоколов. Мы можем строить наши собственные 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) } 
} 

Глубокая магия здесь validator. Возможно, есть еще один способ сделать стирание стилей без закрытия, но это лучший способ, который я знаю. (Я ненавижу тот факт, что Свифт не может справиться validate быть свойством закрытия. В Свифта, добытчики собственности не являются собственными методами. Таким образом, вам необходимо дополнительное разыменования слой validator.)

С, что на месте, вы можете сделать типы массивов, которые вы хотели:

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

Обратите внимание, что тип стирания не выбрасывает тип безопасности. Он просто «стирает» уровень детализации реализации. AnyValidationRule<String> все еще отличается от AnyValidationRule<Int>, так что это не получится:

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

Это отличный ответ, спасибо. Я не думаю, что раньше когда-либо видел подобное. Откуда вы это узнали? Заинтересованы в чтении еще. –

+3

Мой любимый источник - это сам файл заголовка Swift. Там есть тонна документации, в том числе немало «почему», если вы внимательно прочитаете ее. И я трачу много времени, пытаясь построить странные структуры данных и жалуясь на это на Твиттере, когда они не работают, и @jckarter исправить меня. http://airspeedvelocity.net также является очень хорошим источником для интересующих меня тем. –

+0

Почему вам нужно закрыть и не может просто хранить «базу» и проверять правильность на хранящейся базе, когда вы хотите вызвать подтверждение? – JPC

Смежные вопросы