Comment append différents types conforms à un protocole avec un type associé à une collection?

Comme exercice d'apprentissage, je réécris ma bibliothèque de validation dans Swift.

J'ai un protocole ValidationRule qui définit les règles individuelles:

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

Le type associé InputType définit le type d'input à valider (par exemple Ssortingng). Cela peut être explicite ou générique.

Voici deux règles:

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

Ailleurs, j'ai une fonction qui valide une input avec une collection de 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) } 

Je pensais que cela allait fonctionner mais le compilateur n'est pas d'accord.

Dans l'exemple suivant, même si l'input est une string, le rule1 de InputType est une string, et le rule2 InputType est une string …

 func testThatItCanEvaluateMultipleRules() { let rule1 = ValidationRuleCondition<Ssortingng>(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"])) } 

… Je reçois un message d'erreur extrêmement utile:

_ n'est pas convertible en ValidationRuleLength

ce qui est cryptique mais suggère que les types doivent être exactement égaux?

Donc ma question est … comment puis-je append différents types qui se conforment tous à un protocole avec un type associé dans une collection?

Je ne sais pas comment réaliser ce que je tente, ou si c'est même possible?

MODIFIER

Voici c'est sans context:

 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 = Ssortingng func doSomething(thing: Ssortingng) { print(thing) } } func doSomethingWithFoos<F: Foo>(thing: [F]) { print(thing) } let bar = Bar<Ssortingng>() let baz = Baz() let foos: [Foo] = [bar, baz] doSomethingWithFoos(foos) 

Ici nous obtenons:

Le protocole Foo ne peut être utilisé que comme contrainte générique car il a des exigences de type Self ou associées.

Je comprends que. Ce que j'ai besoin de dire est quelque chose comme:

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

    Les protocoles avec des alias de type ne peuvent pas être utilisés de cette manière. Swift n'a pas de moyen de parler directement de méta-types comme ValidationRule ou Array . Vous ne pouvez gérer que des instanciations telles que ValidationRule where... ou Array<Ssortingng> . Avec typealiases, il n'y a aucun moyen d'y arriver directement. Nous devons donc y arriver indirectement avec un effacement de type.

    Swift a plusieurs types-gommes. AnySequence , AnyGenerator , AnyForwardIndex , etc. Ce sont des versions generics de protocoles. Nous pouvons build notre propre 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 magie profonde ici est validator . Il est possible qu'il y ait une autre façon de faire un effacement de type sans fermeture, mais c'est la meilleure façon que je connaisse. (Je déteste aussi le fait que Swift ne puisse pas gérer validate étant une propriété de fermeture Dans Swift, les getters de propriétés ne sont pas des methods correctes, donc vous avez besoin de la couche d'indirection supplémentaire de validator .)

    Avec cela en place, vous pouvez faire les types de arrays que vous vouliez:

     let len = ValidationRuleLength() len.validate("stuff") let cond = ValidationRuleCondition<Ssortingng>() cond.validate("otherstuff") let rules = [AnyValidationRule(len), AnyValidationRule(cond)] let passed = rules.reduce(true) { $0 && $1.validate("combined") } 

    Notez que l'effacement de type ne jette pas la security de type. Il "efface" simplement une couche de détail d'implémentation. AnyValidationRule<Ssortingng> est toujours différent de AnyValidationRule<Int> , donc cela échouera:

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