如何将符合具有关联类型的协议的不同类型添加到集合中? [英] How do I add different types conforming to a protocol with an associated type to a collection?

查看:27
本文介绍了如何将符合具有关联类型的协议的不同类型添加到集合中?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

作为学习练习,我正在用 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
    // ...
}

在其他地方,我有一个函数可以使用 ValidationRules 的集合来验证输入:

Elsewhere, I have a function that validates an input with a collection of ValidationRules:

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,rule1InputType是一个String,而rule2>InputType 是一个字符串...

In the following example, even though the input is a String, rule1's InputType is a String, and rule2s 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 没有办法直接讨论像 ValidationRuleArray 这样的元类型.您只能处理诸如 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 有几种类型橡皮擦.AnySequenceAnyGeneratorAnyForwardIndex 等.这些是协议的通用版本.我们可以构建自己的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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆