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

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

问题描述

作为学习练习,我正在使用Swift重写我的验证库



我有一个 ValidationRule 协议,它定义了单独的规则应该是这样的:

< pre $ protocol ValidationRule {
typealias InputType
func validateInput(input:InputType) - > Bool
// ...
}

关联的类型 InputType 定义了要验证的输入类型(例如String)。它可以是明确的或通用的。

这里有两条规则:

  struct ValidationRuleLength:ValidationRule {
typealias InputType = String
// ...
}

结构ValidationRuleCondition< T>:ValidationRule {
typealias InputType = T
/ / ...
}

其他地方,我有一个函数用于验证输入收集 ValidationRule s:

  static func validate< R:ValidationRule> (输入i:R.InputType,规则rs:[R]) - > ValidationResult {
let errors = rs.filter {!$ 0.validateInput(i)} .map {$ 0.failureMessage}
return errors.isEmpty? .Valid:.Invalid(errors)
}

我认为这会起作用,但编译器不同意。

在下面的例子中,即使输入是一个字符串, rule1 InputType 是一个字符串,并且 rule2 s InputType 是一个String ...



  func testThatItCanEvaluateMultipleRules(){

let rule1 = ValidationRuleCondition< String>(failureMessage:message1){ $ 0.characters.count> 0}
let rule2 = ValidationRuleLength(min:1,failureMessage:message2)
$ b $无效= Validator.validate(输入:,rules:[rule1,rule2])
XCTAssertEqual(invalid,.Invalid([message1,message2]))

}

...我收到了非常有帮助的错误消息:


_不能转换为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){$ b $ 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只能用作通用约束,因为它具有
自我或相关类型的要求。


我明白这一点。我需要说的是这样的:

  doSomethingWithFoos< F:Foo其中F.FooType == F.FooType>(东西:[F]){

}


解决方案

具有别名类型的协议不能以这种方式使用。 Swift没有办法直接讨论元类型,比如 ValidationRule Array 。您只能处理实例化,如 ValidationRule其中... 数组< String> 。使用类型别名,无法直接进入。所以我们必须间接使用类型擦除。



Swift有几种类型橡皮擦。 AnySequence AnyGenerator AnyForwardIndex 等等。协议版本。我们可以构建自己的 AnyValidationRule


$ b

  struct AnyValidationRule< InputType> ;: ValidationRule {
private let validator:(InputType) - > Bool
init< Base:ValidationRule其中Base.InputType == InputType>(_ base:Base){
validator = base.validate
}
func validate(input:InputType) - > Bool {return validator(input)}
}

这里的深层魔法是验证。有可能还有其他方法可以在没有关闭的情况下进行类型擦除,但这是我知道的最好方式。 (我也讨厌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) {$ 0&& $ 1.validate(combined)}

请注意,类型擦除不会丢弃类型安全性。它只是擦除了一层实现细节。 AnyValidationRule< String> 仍然不同于 AnyValidationRule< Int> ,所以这会失败:

  let len = ValidationRuleLength()
let condInt = ValidationRuleCondition< Int>()
let badRules = [AnyValidationRule(len), AnyValidationRule(condInt)]
//错误:表达式类型不明确,没有更多上下文


As an exercise in learning I'm rewriting my validation library in Swift.

I have a ValidationRule protocol that defines what individual rules should look like:

protocol ValidationRule {
    typealias InputType
    func validateInput(input: InputType) -> Bool
    //...
}

The associated type InputType defines the type of input to be validated (e.g String). It can be explicit or generic.

Here are two rules:

struct ValidationRuleLength: ValidationRule {
    typealias InputType = String
    //...
}

struct ValidationRuleCondition<T>: ValidationRule {   
    typealias InputType = T
    // ...
}

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.

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:

_ 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?

EDIT

Here's it is without context:

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)

Here we get:

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]) {

}

解决方案

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 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) }
}

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") }

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天全站免登陆