如何将符合具有关联类型的协议的不同类型添加到集合中? [英] How do I add different types conforming to a protocol with an associated type to a collection?
问题描述
作为学习练习,我正在用 Swift 重写我的验证库.
As an exercise in learning I'm rewriting my validation library in Swift.
我有一个 ValidationRule
协议,它定义了各个规则应该是什么样子:
I have a ValidationRule
protocol that defines what individual rules should look like:
protocol ValidationRule {
typealias InputType
func validateInput(input: InputType) -> Bool
//...
}
关联类型 InputType
定义要验证的输入类型(例如字符串).它可以是显式的或通用的.
The associated type InputType
defines the type of input to be validated (e.g String). It can be explicit or generic.
这里有两个规则:
struct ValidationRuleLength: ValidationRule {
typealias InputType = String
//...
}
struct ValidationRuleCondition<T>: ValidationRule {
typealias InputType = T
// ...
}
在其他地方,我有一个函数可以使用 ValidationRule
s 的集合来验证输入:
Elsewhere, I have a function that validates an input with a collection of 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)
}
我认为这会起作用,但编译器不同意.
I thought this was going to work but the compiler disagrees.
在下面的例子中,即使输入是一个String,rule1
的InputType
是一个String,而rule2
的>InputType
是一个字符串...
In the following example, even though the input is a String, rule1
's InputType
is a String, and rule2
s InputType
is a 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"]))
}
...我收到了非常有用的错误消息:
... I'm getting extremely helpful error message:
_ 不能转换为 ValidationRuleLength
_ is not convertible to ValidationRuleLength
这很神秘,但表明类型应该完全相等?
which is cryptic but suggests that the types should be exactly equal?
所以我的问题是......我如何将所有符合协议的不同类型附加到一个集合中?
So my question is... how do I append different types that all conform to a protocol with an associated type into a collection?
不确定如何实现我正在尝试的目标,或者是否有可能实现?
Unsure how to achieve what I'm attempting, or if it's even possible?
编辑
这里没有上下文:
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 只能用作通用约束,因为它具有自身或相关类型要求.
Protocol Foo can only be used as a generic constraint because it has Self or associated type requirements.
我明白.我需要说的是:
I understand that. What I need to say is something like:
doSomethingWithFoos<F: Foo where F.FooType == F.FooType>(thing: [F]) {
}
推荐答案
不能以这种方式使用带有类型别名的协议.Swift 没有办法直接讨论像 ValidationRule
或 Array
这样的元类型.您只能处理诸如 ValidationRule where...
或 Array
之类的实例化.使用类型别名,无法直接到达那里.所以我们必须通过类型擦除来间接实现.
Protocols with type aliases cannot be used this way. Swift doesn't have a way to talk directly about meta-types like ValidationRule
or Array
. You can only deal with instantiations like ValidationRule where...
or Array<String>
. With typealiases, there's no way to get there directly. So we have to get there indirectly with type erasure.
Swift 有几种类型橡皮擦.AnySequence
、AnyGenerator
、AnyForwardIndex
等.这些是协议的通用版本.我们可以构建自己的AnyValidationRule
:
Swift has several type-erasers. AnySequence
, AnyGenerator
, AnyForwardIndex
, etc. These are generic versions of protocols. We can build our own 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 中,属性 getter 不是正确的方法.因此您需要额外的 validator
间接层.)
The deep magic here is validator
. It's possible that there's some other way to do type erasure without a closure, but that's the best way I know. (I also hate the fact that Swift cannot handle validate
being a closure property. In Swift, property getters aren't proper methods. So you need the extra indirection layer of validator
.)
有了它,你就可以制作你想要的数组类型:
With that in place, you can make the kinds of arrays you wanted:
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
仍然与 AnyValidationRule
不同,所以会失败:
Note that type erasure doesn't throw away type safety. It just "erases" a layer of implementation detail. AnyValidationRule<String>
is still different from AnyValidationRule<Int>
, so this will fail:
let len = ValidationRuleLength()
let condInt = ValidationRuleCondition<Int>()
let badRules = [AnyValidationRule(len), AnyValidationRule(condInt)]
// error: type of expression is ambiguous without more context
这篇关于如何将符合具有关联类型的协议的不同类型添加到集合中?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!