如何将符合具有关联类型的协议的不同类型添加到集合中?
How do I add different types conforming to a protocol with an associated type to a collection?
作为学习练习,我正在 Swift 中重写我的 validation library。
我有一个 ValidationRule
协议,它定义了各个规则应该是什么样子:
protocol ValidationRule {
typealias InputType
func validateInput(input: InputType) -> Bool
//...
}
关联类型 InputType
定义要验证的输入类型(例如字符串)。它可以是明确的或通用的。
这里有两条规则:
struct ValidationRuleLength: ValidationRule {
typealias InputType = String
//...
}
struct ValidationRuleCondition<T>: ValidationRule {
typealias InputType = T
// ...
}
在其他地方,我有一个函数可以使用 ValidationRule
s:
的集合来验证输入
static func validate<R: ValidationRule>(input i: R.InputType, rules rs: [R]) -> ValidationResult {
let errors = rs.filter { ![=12=].validateInput(i) }.map { [=12=].failureMessage }
return errors.isEmpty ? .Valid : .Invalid(errors)
}
我认为这会起作用,但编译器不同意。
在下面的示例中,即使输入是一个字符串,rule1
的 InputType
也是一个字符串,rule2
s InputType
也是一个字符串。 ..
func testThatItCanEvaluateMultipleRules() {
let rule1 = ValidationRuleCondition<String>(failureMessage: "message1") { [=13=].characters.count > 0 }
let rule2 = ValidationRuleLength(min: 1, failureMessage: "message2")
let invalid = Validator.validate(input: "", rules: [rule1, rule2])
XCTAssertEqual(invalid, .Invalid(["message1", "message2"]))
}
...我收到了非常有用的错误消息:
_ is not convertible to ValidationRuleLength
这有点含糊,但表明类型应该完全相等?
所以我的问题是...如何将所有都符合具有关联类型的协议的不同类型附加到集合中?
不确定如何实现我正在尝试的目标,或者这是否可能?
编辑
这是没有上下文的:
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)
这里我们得到:
Protocol Foo can only be used as a generic constraint because it has
Self or associated type requirements.
我明白了。我需要说的是:
doSomethingWithFoos<F: Foo where F.FooType == F.FooType>(thing: [F]) {
}
不能以这种方式使用带有类型别名的协议。 Swift 没有办法直接讨论像 ValidationRule
或 Array
这样的元类型。您只能处理像 ValidationRule where...
或 Array<String>
这样的实例化。使用类型别名,无法直接到达那里。所以我们必须通过类型擦除间接到达那里。
Swift 有几个类型橡皮擦。 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
。可能有其他方法可以在不关闭的情况下进行类型擦除,但这是我所知道的最好的方法。 (我也讨厌 Swift 无法处理 validate
作为闭包 属性 的事实。在 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) { [=11=] && .validate("combined") }
请注意,类型擦除不会丢弃类型安全。它只是 "erases" 一层实现细节。 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
作为学习练习,我正在 Swift 中重写我的 validation library。
我有一个 ValidationRule
协议,它定义了各个规则应该是什么样子:
protocol ValidationRule {
typealias InputType
func validateInput(input: InputType) -> Bool
//...
}
关联类型 InputType
定义要验证的输入类型(例如字符串)。它可以是明确的或通用的。
这里有两条规则:
struct ValidationRuleLength: ValidationRule {
typealias InputType = String
//...
}
struct ValidationRuleCondition<T>: ValidationRule {
typealias InputType = T
// ...
}
在其他地方,我有一个函数可以使用 ValidationRule
s:
static func validate<R: ValidationRule>(input i: R.InputType, rules rs: [R]) -> ValidationResult {
let errors = rs.filter { ![=12=].validateInput(i) }.map { [=12=].failureMessage }
return errors.isEmpty ? .Valid : .Invalid(errors)
}
我认为这会起作用,但编译器不同意。
在下面的示例中,即使输入是一个字符串,rule1
的 InputType
也是一个字符串,rule2
s InputType
也是一个字符串。 ..
func testThatItCanEvaluateMultipleRules() {
let rule1 = ValidationRuleCondition<String>(failureMessage: "message1") { [=13=].characters.count > 0 }
let rule2 = ValidationRuleLength(min: 1, failureMessage: "message2")
let invalid = Validator.validate(input: "", rules: [rule1, rule2])
XCTAssertEqual(invalid, .Invalid(["message1", "message2"]))
}
...我收到了非常有用的错误消息:
_ is not convertible to ValidationRuleLength
这有点含糊,但表明类型应该完全相等?
所以我的问题是...如何将所有都符合具有关联类型的协议的不同类型附加到集合中?
不确定如何实现我正在尝试的目标,或者这是否可能?
编辑
这是没有上下文的:
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)
这里我们得到:
Protocol Foo can only be used as a generic constraint because it has Self or associated type requirements.
我明白了。我需要说的是:
doSomethingWithFoos<F: Foo where F.FooType == F.FooType>(thing: [F]) {
}
不能以这种方式使用带有类型别名的协议。 Swift 没有办法直接讨论像 ValidationRule
或 Array
这样的元类型。您只能处理像 ValidationRule where...
或 Array<String>
这样的实例化。使用类型别名,无法直接到达那里。所以我们必须通过类型擦除间接到达那里。
Swift 有几个类型橡皮擦。 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
。可能有其他方法可以在不关闭的情况下进行类型擦除,但这是我所知道的最好的方法。 (我也讨厌 Swift 无法处理 validate
作为闭包 属性 的事实。在 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) { [=11=] && .validate("combined") }
请注意,类型擦除不会丢弃类型安全。它只是 "erases" 一层实现细节。 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