ios - ¿Cómo agrego diferentes tipos que se ajustan a un protocolo con un tipo asociado a una colección?
swift generics (1)
Los protocolos con alias de tipo no se pueden usar de esta manera.
Swift no tiene una manera de hablar directamente sobre metatipos como
ValidationRule
o
Array
.
Solo puede tratar instancias como
ValidationRule where...
o
Array<String>
.
Con los typealiases, no hay forma de llegar allí directamente.
Entonces, tenemos que llegar allí indirectamente con borrado de tipo.
Swift tiene varios tipos de borradores.
AnySequence
,
AnyGenerator
,
AnyForwardIndex
, etc. Estas son versiones genéricas de protocolos.
Podemos construir nuestra propia
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 profunda aquí es el
validator
.
Es posible que haya otra forma de borrar el tipo sin un cierre, pero esa es la mejor manera que conozco.
(También odio el hecho de que Swift no puede manejar la
validate
como una propiedad de cierre. En Swift, los captadores de propiedades no son métodos adecuados. Por lo tanto, necesita la capa adicional de
validator
indirecta).
Con eso en su lugar, puede hacer los tipos de matrices que desea:
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") }
Tenga en cuenta que la eliminación de tipo no descarta la seguridad de tipo.
Simplemente "borra" una capa de detalles de implementación.
AnyValidationRule<String>
sigue siendo diferente de
AnyValidationRule<Int>
, por lo que esto fallará:
let len = ValidationRuleLength()
let condInt = ValidationRuleCondition<Int>()
let badRules = [AnyValidationRule(len), AnyValidationRule(condInt)]
// error: type of expression is ambiguous without more context
Como ejercicio de aprendizaje, estoy reescribiendo mi biblioteca de validación en Swift.
Tengo un protocolo
ValidationRule
que define cómo deberían verse las reglas individuales:
protocol ValidationRule {
typealias InputType
func validateInput(input: InputType) -> Bool
//...
}
El tipo asociado
InputType
define el tipo de entrada que se validará (por ejemplo, String).
Puede ser explícito o genérico.
Aquí hay dos reglas:
struct ValidationRuleLength: ValidationRule {
typealias InputType = String
//...
}
struct ValidationRuleCondition<T>: ValidationRule {
typealias InputType = T
// ...
}
En otro lugar, tengo una función que valida una entrada con una colección de
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)
}
Pensé que esto iba a funcionar, pero el compilador no está de acuerdo.
En el siguiente ejemplo, aunque la entrada es una Cadena, el
rule1
de la
InputType
es una Cadena, y el
rule2
la
InputType
es una Cadena ...
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"]))
}
... recibo un mensaje de error extremadamente útil:
_ no es convertible a ValidationRuleLength
que es críptico pero sugiere que los tipos deberían ser exactamente iguales?
Entonces mi pregunta es ... ¿cómo agrego diferentes tipos que se ajustan a un protocolo con un tipo asociado en una colección?
¿No estás seguro de cómo lograr lo que intento o si es posible?
EDITAR
Aquí está sin contexto:
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)
Aquí tenemos:
Protocol Foo solo se puede usar como una restricción genérica porque tiene requisitos de tipo Self o asociados.
Entiendo que. Lo que necesito decir es algo como:
doSomethingWithFoos<F: Foo where F.FooType == F.FooType>(thing: [F]) {
}