如何将符合相关types的协议的不同types添加到集合中?
作为学习练习,我正在用Swift重写我的validation库 。
我有一个ValidationRule
协议,它定义了什么样的个人规则:
protocol ValidationRule { typealias InputType func validateInput(input: InputType) -> Bool //... }
关联的typesInputType
定义了要validation的input的types(例如String)。 它可以是明确的或通用的。
这里有两条规则:
struct ValidationRuleLength: ValidationRule { typealias InputType = String //... } struct ValidationRuleCondition<T>: ValidationRule { typealias InputType = T // ... }
在其他地方,我有一个函数用一个ValidationRule
集合来ValidationRule
input:
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) }
我认为这是行得通的,但编译器不同意。
在以下示例中,即使input是string, rule1
的InputType
是一个String,而InputType
是一个String …
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
这是神秘的,但build议的types应该是完全相等的?
所以我的问题是…我如何追加不同types的所有符合协议与关联types到一个集合?
不确定如何实现我所尝试的,或者甚至是可能的?
编辑
这是没有上下文的:
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只能用作通用约束,因为它具有自我或相关types的要求。
我明白那个。 我需要说的是这样的:
doSomethingWithFoos<F: Foo where F.FooType == F.FooType>(thing: [F]) { }
具有别名types的协议不能以这种方式使用。 Swift没有办法直接谈论像ValidationRule
或Array
这样的元types。 你只能处理像ValidationRule where...
实例ValidationRule where...
或者Array<String>
。 使用types,没有办法直接到达那里。 所以我们必须间接地进行types擦除。
斯威夫特有几个types的橡皮擦。 AnySequence
, AnyGenerator
, AnyForwardIndex
等。这些是协议的通用版本。 我们可以build立我们自己的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
。 有可能还有其他方法可以在没有closures的情况下进行types擦除,但这是我所知道的最好的方式。 (我也讨厌Swift无法处理validate
是closures属性的事实,在Swift中,属性获取器不是合适的方法,所以你需要额外的间接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") }
请注意,types擦除不会丢弃types安全。 它只是“擦除”了一层实现细节。 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